From c8328cb57b9b23c474ab0c55ca5caa0e90bc5f80 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:33 -0400 Subject: [PATCH 01/94] Coff: Implement writing the export table --- lib/std/coff.zig | 44 +++++++ src/link/Coff.zig | 313 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 347 insertions(+), 10 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 6286ad5657b0a660baa2fb7d3966aadc02b3572b..17cef8d412e85fc0445be733d6d4d6ef92adb69f 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -452,6 +452,50 @@ pub const ImportHintNameEntry = extern struct { name: [1]u8, }; +pub const ExportDirectoryTable = extern struct { + /// Reserved + flags: u32, + + /// Creation time of this table + time_date_stamp: u32, + + major_version: u16, + minor_version: u16, + + /// The address of an ASCII string that contains the name of the DLL. + /// This address is relative to the image base. + name_rva: u32, + + /// The ordinal of the first export in this image + ordinal_base: u32, + + /// Number of entries in the export address table + number_of_entries: u32, + + /// Number of entries in the name pointer table and ordinal table + number_of_names: u32, + + export_address_table_rva: u32, + name_pointer_table_rva: u32, + ordinal_table_rva: u32, +}; + +pub const ExportAddressTableEntry = extern struct { + /// If this address is within the export section, then this is the address of the export + /// Otherwise, this is the address of a string that specfies a symbol in another DLL: + /// . + /// .# + export_or_forwarder_rva: u32, +}; + +pub const ExportNamePointerTableEntry = extern struct { + name_rva: u32, +}; + +pub const ExportOrdinalTableEntry = extern struct { + unbiased_ordinal: u16, +}; + pub const SectionHeader = extern struct { name: [8]u8, virtual_size: u32, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7136131f2f0ef015683f6bf915e62251f5d0a233..eee866e34d9a4935d6f1ce462ac92be5834bf82b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -22,6 +22,7 @@ base: link.File, mf: MappedFile, nodes: std.MultiArrayList(Node), import_table: ImportTable, +export_table: ExportTable, strings: std.HashMapUnmanaged( u32, void, @@ -146,6 +147,12 @@ pub const Node = union(enum) { import_address_table: ImportTable.Index, import_hint_name_table: ImportTable.Index, + export_directory_table, + export_address_table, + export_name_pointer_table, + export_ordinal_table, + export_name_table, + pseudo_section: PseudoSectionMapIndex, object_section: ObjectSectionMapIndex, global: GlobalMapIndex, @@ -270,6 +277,44 @@ pub const Node = union(enum) { } }; +pub const ExportTable = struct { + ni: MappedFile.Node.Index, + export_address_table_ni: MappedFile.Node.Index, + name_pointer_table_ni: MappedFile.Node.Index, + ordinal_table_ni: MappedFile.Node.Index, + name_table_ni: MappedFile.Node.Index, + entries: std.AutoArrayHashMapUnmanaged(void, Entry), + + pub const Entry = struct { + name_index: u32, + name_len: u32, + }; + + const Adapter = struct { + coff: *Coff, + + pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool { + const coff = adapter.coff; + const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); + const rhs = coff.export_table.entries.values()[rhs_index]; + return std.mem.eql(u8, name_table_slice[rhs.name_index..][0..rhs.name_len], lhs_key); + } + + pub fn hash(_: Adapter, key: []const u8) u32 { + assert(std.mem.indexOfScalar(u8, key, 0) == null); + return std.array_hash_map.hashString(key); + } + }; + + pub const Index = enum(u32) { + _, + + pub fn get(export_index: ExportTable.Index, coff: *Coff) *Entry { + return &coff.export_table.entries.values()[@intFromEnum(export_index)]; + } + }; +}; + pub const ImportTable = struct { ni: MappedFile.Node.Index, entries: std.array_hash_map.Auto(void, Entry), @@ -314,6 +359,7 @@ pub const String = enum(u32) { @".rdata" = 13, @".text" = 20, @".tls$" = 26, + @".edata" = 32, _, pub const Optional = enum(u32) { @@ -321,6 +367,7 @@ pub const String = enum(u32) { @".rdata" = @intFromEnum(String.@".rdata"), @".text" = @intFromEnum(String.@".text"), @".tls$" = @intFromEnum(String.@".tls$"), + @".edata" = @intFromEnum(String.@".edata"), none = std.math.maxInt(u32), _, @@ -695,6 +742,14 @@ fn create( .ni = .none, .entries = .empty, }, + .export_table = .{ + .ni = .none, + .export_address_table_ni = .none, + .name_pointer_table_ni = .none, + .ordinal_table_ni = .none, + .name_table_ni = .none, + .entries = .empty, + }, .strings = .empty, .string_bytes = .empty, .image_section_table = .empty, @@ -741,6 +796,7 @@ pub fn deinit(coff: *Coff) void { coff.mf.deinit(gpa); coff.nodes.deinit(gpa); coff.import_table.entries.deinit(gpa); + coff.export_table.entries.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); coff.image_section_table.deinit(gpa); @@ -780,7 +836,7 @@ fn initHeaders( else 0; - const expected_nodes_len = Node.known_count + 6 + + const expected_nodes_len = Node.known_count + 12 + @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2; try coff.nodes.ensureTotalCapacity(gpa, expected_nodes_len); coff.nodes.appendAssumeCapacity(.file); @@ -1005,6 +1061,67 @@ fn initHeaders( ); coff.nodes.appendAssumeCapacity(.import_directory_table); + { + // TODO: Could create this lazily when processing the first export instead? + const edata_section_ni = (try coff.pseudoSectionMapIndex( + .@".edata", + .of(std.coff.ExportDirectoryTable), + .{ .read = true }, + )).symbol(coff).node(coff); + + const name = "TODO_NAME.dll"; + const name_index = @sizeOf(std.coff.ExportDirectoryTable); + coff.export_table.ni = try coff.mf.addLastChildNode( + gpa, + edata_section_ni, + .{ + .size = @sizeOf(std.coff.ExportDirectoryTable) + name.len + 1, + .alignment = .of(std.coff.ExportDirectoryTable), + .fixed = true, + .moved = true, + }, + ); + @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); + + coff.export_table.export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ + .alignment = .of(u32), + .moved = true, + }); + coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ + .alignment = .of(u32), + .moved = true, + }); + coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ + .alignment = .of(u16), + .moved = true, + }); + coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ + .alignment = .of(u8), + .moved = true, + }); + + coff.nodes.appendAssumeCapacity(.export_directory_table); + coff.nodes.appendAssumeCapacity(.export_address_table); + coff.nodes.appendAssumeCapacity(.export_name_pointer_table); + coff.nodes.appendAssumeCapacity(.export_ordinal_table); + coff.nodes.appendAssumeCapacity(.export_name_table); + + const export_directory_table = coff.exportDirectoryTable(); + export_directory_table.* = .{ + .flags = 0, + .time_date_stamp = timestamp, + .major_version = 0, + .minor_version = 0, + .name_rva = 0, + .ordinal_base = 1, + .number_of_entries = 0, + .number_of_names = 0, + .export_address_table_rva = 0, + .name_pointer_table_rva = 0, + .ordinal_table_rva = 0, + }; + } + // While tls variables allocated at runtime are writable, the template itself is not if (comp.config.any_non_single_threaded) _ = try coff.objectSectionMapIndex( .@".tls$", @@ -1048,6 +1165,7 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .optional_header, .data_directories, .section_table, + .export_name_table, => unreachable, .image_section => |si| si, .import_directory_table => break :parent_rva coff.targetLoad( @@ -1062,6 +1180,18 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .import_hint_name_table => |import_index| break :parent_rva coff.targetLoad( &coff.importDirectoryEntryPtr(import_index).name_rva, ), + .export_directory_table => break :parent_rva coff.targetLoad( + &coff.dataDirectoryPtr(.EXPORT).virtual_address, + ), + .export_address_table => break :parent_rva coff.targetLoad( + &coff.exportDirectoryTable().export_address_table_rva, + ), + .export_name_pointer_table => break :parent_rva coff.targetLoad( + &coff.exportDirectoryTable().name_pointer_table_rva, + ), + .export_ordinal_table => break :parent_rva coff.targetLoad( + &coff.exportDirectoryTable().ordinal_table_rva, + ), inline .pseudo_section, .object_section, .global, @@ -1181,6 +1311,22 @@ pub fn importDirectoryEntryPtr( return &coff.importDirectoryTableSlice()[@intFromEnum(import_index)]; } +pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable { + return @ptrCast(@alignCast(coff.export_table.ni.slice(&coff.mf))); +} + +pub fn exportAddressTableSlice(coff: *Coff) []std.coff.ExportAddressTableEntry { + return @ptrCast(@alignCast(coff.export_table.export_address_table_ni.slice(&coff.mf))); +} + +pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry { + return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf))); +} + +pub fn exportOrdinalTableSlice(coff: *Coff) []std.coff.ExportOrdinalTableEntry { + return @ptrCast(@alignCast(coff.export_table.ordinal_table_ni.slice(&coff.mf))); +} + fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { defer coff.symbol_table.addOneAssumeCapacity().* = .{ .ni = .none, @@ -1729,6 +1875,82 @@ pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void { }; } +fn flushExports(coff: *Coff, tid: Zcu.PerThread.Id) !void { + const export_count = coff.export_table.entries.count(); + if (export_count == 0) return; + + const gpa = coff.base.comp.zcu.?.gpa; + const edt = coff.exportDirectoryTable(); + edt.number_of_names = @intCast(export_count); + edt.number_of_entries = @intCast(export_count); + + try coff.export_table.name_pointer_table_ni.resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportNamePointerTableEntry), + ); + + try coff.export_table.ordinal_table_ni.resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportOrdinalTableEntry), + ); + + while (try coff.idle(tid)) {} + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, edt); + + const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni); + for ( + coff.exportNamePointerTableSlice(), + coff.exportOrdinalTableSlice(), + coff.export_table.entries.values(), + 0.., + ) |*np, *ord, entry, entry_i| { + np.name_rva = name_table_rva + entry.name_index; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.ExportNamePointerTableEntry, np); + + ord.unbiased_ordinal = @intCast(entry_i); + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.ExportOrdinalTableEntry, &ord); + } + + const Context = struct { + np: []std.coff.ExportNamePointerTableEntry, + ord: []std.coff.ExportOrdinalTableEntry, + entries: []ExportTable.Entry, + names: []const u8, + + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + const lhs_entry = &ctx.entries[lhs]; + const rhs_entry = &ctx.entries[rhs]; + return std.mem.lessThan( + u8, + ctx.names[lhs_entry.name_index..][0..lhs_entry.name_len], + ctx.names[rhs_entry.name_index..][0..rhs_entry.name_len], + ); + } + + pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { + std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]); + std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]); + std.mem.swap(ExportTable.Entry, &ctx.entries[lhs], &ctx.entries[rhs]); + } + }; + + std.sort.pdqContext(0, export_count, Context{ + .np = coff.exportNamePointerTableSlice(), + .ord = coff.exportOrdinalTableSlice(), + .entries = coff.export_table.entries.values(), + .names = coff.export_table.name_table_ni.slice(&coff.mf), + }); + + // TODO: Is there a way to know if this is the last flush? We could skip doing this if so. + // TODO: Need to reindex with adaptor? + //try coff.export_table.entries.reIndexContext(gpa, ExportTable.Adapter{ .coff = coff }); +} + pub fn flush( coff: *Coff, arena: std.mem.Allocator, @@ -1739,6 +1961,9 @@ pub fn flush( _ = prog_node; while (try coff.idle(tid)) {} + coff.flushExports(tid) catch |err| + return coff.base.comp.link_diags.fail("linker failed to flush exports: {t}", .{err}); + // hack for stage2_x86_64 + coff const comp = coff.base.comp; if (comp.compiler_rt_dyn_lib) |crt_file| { @@ -1932,19 +2157,20 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const comp = zcu.comp; const gpa = zcu.gpa; const gn = gmi.globalName(coff); + + const target_endian = coff.targetEndian(); + const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); + const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { + _ => unreachable, + .PE32 => .{ 4, .@"4" }, + .@"PE32+" => .{ 8, .@"8" }, + }; + + const name = gn.name.toSlice(coff); if (gn.lib_name.toSlice(coff)) |lib_name| { - const name = gn.name.toSlice(coff); try coff.nodes.ensureUnusedCapacity(gpa, 4); try coff.symbol_table.ensureUnusedCapacity(gpa, 1); - const target_endian = coff.targetEndian(); - const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); - const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { - _ => unreachable, - .PE32 => .{ 4, .@"4" }, - .@"PE32+" => .{ 8, .@"8" }, - }; - const gop = try coff.import_table.entries.getOrPutAdapted( gpa, lib_name, @@ -2089,6 +2315,47 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); + } else { + const entries_ctx = ExportTable.Adapter{ .coff = coff }; + const gop = try coff.export_table.entries.getOrPutAdapted( + gpa, + name, + entries_ctx, + ); + + if (!gop.found_existing) { + errdefer _ = coff.export_table.entries.pop(); + if (coff.export_table.entries.count() > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) + return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); + + const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size; + const new_name_table_size = name_index + name.len + 1; + if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) + return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); + + try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size); + + const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); + @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); + + gop.value_ptr.* = .{ + .name_index = @intCast(name_index), + .name_len = @intCast(name.len), + }; + + const si = gmi.symbol(coff); + const sym = si.get(coff); + + try coff.export_table.export_address_table_ni.resize( + &coff.mf, + gpa, + coff.export_table.entries.count() * @sizeOf(std.coff.ExportAddressTableEntry), + ); + const ea = &coff.exportAddressTableSlice()[gop.index]; + ea.export_or_forwarder_rva = sym.rva; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.ExportAddressTableEntry, &ea); + } } } @@ -2218,6 +2485,27 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { import_hint_name_index += 2; } }, + .export_directory_table => { + const rva = coff.computeNodeRva(ni); + coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva); + coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable)); + }, + .export_address_table => coff.targetStore( + &coff.exportDirectoryTable().export_address_table_rva, + coff.computeNodeRva(ni), + ), + .export_name_pointer_table => coff.targetStore( + &coff.exportDirectoryTable().name_pointer_table_rva, + coff.computeNodeRva(ni), + ), + .export_ordinal_table => coff.targetStore( + &coff.exportDirectoryTable().ordinal_table_rva, + coff.computeNodeRva(ni), + ), + .export_name_table => { + // .export_name_pointer_table entries are updated in flush + log.warn("flushMoved export_name_table unhandled", .{}); + }, inline .pseudo_section, .object_section, .global, @@ -2272,6 +2560,11 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { @intCast(size), ), .import_lookup_table, .import_address_table, .import_hint_name_table => {}, + .export_directory_table => coff.targetStore( + &coff.dataDirectoryPtr(.EXPORT).size, + @intCast(size), + ), + .export_address_table, .export_name_pointer_table, .export_ordinal_table, .export_name_table => {}, inline .pseudo_section, .object_section, => |smi| smi.symbol(coff).get(coff).size = @intCast(size), -- 2.54.0 From a87fc47bc17ae28a8a0a90da4c64751a071bac44 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:33 -0400 Subject: [PATCH 02/94] - Move the export table logic into updateExportsInner - Set up relocs for exported symbols in the export address table - Move the sort to `idle`, and set it up to only occur when necessary - Handle the name table being moved --- src/link/Coff.zig | 336 ++++++++++++++++++++++++++-------------------- 1 file changed, 191 insertions(+), 145 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index eee866e34d9a4935d6f1ce462ac92be5834bf82b..52d12a93cb7df173078b71909bb11bc21ea56fe5 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -279,15 +279,17 @@ pub const Node = union(enum) { pub const ExportTable = struct { ni: MappedFile.Node.Index, - export_address_table_ni: MappedFile.Node.Index, + export_address_table_si: Symbol.Index, name_pointer_table_ni: MappedFile.Node.Index, ordinal_table_ni: MappedFile.Node.Index, name_table_ni: MappedFile.Node.Index, entries: std.AutoArrayHashMapUnmanaged(void, Entry), + pending_sort: bool = false, pub const Entry = struct { name_index: u32, name_len: u32, + export_address_table_ri: Reloc.Index, }; const Adapter = struct { @@ -306,10 +308,10 @@ pub const ExportTable = struct { } }; - pub const Index = enum(u32) { + pub const Ordinal = enum(u16) { _, - pub fn get(export_index: ExportTable.Index, coff: *Coff) *Entry { + pub fn get(export_index: ExportTable.Ordinal, coff: *Coff) *Entry { return &coff.export_table.entries.values()[@intFromEnum(export_index)]; } }; @@ -744,7 +746,7 @@ fn create( }, .export_table = .{ .ni = .none, - .export_address_table_ni = .none, + .export_address_table_si = .null, .name_pointer_table_ni = .none, .ordinal_table_ni = .none, .name_table_ni = .none, @@ -1083,10 +1085,21 @@ fn initHeaders( ); @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); - coff.export_table.export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ + const export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ .alignment = .of(u32), .moved = true, }); + + try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity(); + + const export_address_table_sym = coff.export_table.export_address_table_si.get(coff); + export_address_table_sym.ni = export_address_table_ni; + assert(export_address_table_sym.loc_relocs == .none); + export_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + export_address_table_sym.section_number = + coff.getNode(edata_section_ni).pseudo_section.symbol(coff).get(coff).section_number; + coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ .alignment = .of(u32), .moved = true, @@ -1120,6 +1133,8 @@ fn initHeaders( .name_pointer_table_rva = 0, .ordinal_table_rva = 0, }; + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, export_directory_table); } // While tls variables allocated at runtime are writable, the template itself is not @@ -1315,10 +1330,6 @@ pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable { return @ptrCast(@alignCast(coff.export_table.ni.slice(&coff.mf))); } -pub fn exportAddressTableSlice(coff: *Coff) []std.coff.ExportAddressTableEntry { - return @ptrCast(@alignCast(coff.export_table.export_address_table_ni.slice(&coff.mf))); -} - pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry { return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf))); } @@ -1875,82 +1886,6 @@ pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void { }; } -fn flushExports(coff: *Coff, tid: Zcu.PerThread.Id) !void { - const export_count = coff.export_table.entries.count(); - if (export_count == 0) return; - - const gpa = coff.base.comp.zcu.?.gpa; - const edt = coff.exportDirectoryTable(); - edt.number_of_names = @intCast(export_count); - edt.number_of_entries = @intCast(export_count); - - try coff.export_table.name_pointer_table_ni.resize( - &coff.mf, - gpa, - export_count * @sizeOf(std.coff.ExportNamePointerTableEntry), - ); - - try coff.export_table.ordinal_table_ni.resize( - &coff.mf, - gpa, - export_count * @sizeOf(std.coff.ExportOrdinalTableEntry), - ); - - while (try coff.idle(tid)) {} - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, edt); - - const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni); - for ( - coff.exportNamePointerTableSlice(), - coff.exportOrdinalTableSlice(), - coff.export_table.entries.values(), - 0.., - ) |*np, *ord, entry, entry_i| { - np.name_rva = name_table_rva + entry.name_index; - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportNamePointerTableEntry, np); - - ord.unbiased_ordinal = @intCast(entry_i); - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportOrdinalTableEntry, &ord); - } - - const Context = struct { - np: []std.coff.ExportNamePointerTableEntry, - ord: []std.coff.ExportOrdinalTableEntry, - entries: []ExportTable.Entry, - names: []const u8, - - pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { - const lhs_entry = &ctx.entries[lhs]; - const rhs_entry = &ctx.entries[rhs]; - return std.mem.lessThan( - u8, - ctx.names[lhs_entry.name_index..][0..lhs_entry.name_len], - ctx.names[rhs_entry.name_index..][0..rhs_entry.name_len], - ); - } - - pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { - std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]); - std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]); - std.mem.swap(ExportTable.Entry, &ctx.entries[lhs], &ctx.entries[rhs]); - } - }; - - std.sort.pdqContext(0, export_count, Context{ - .np = coff.exportNamePointerTableSlice(), - .ord = coff.exportOrdinalTableSlice(), - .entries = coff.export_table.entries.values(), - .names = coff.export_table.name_table_ni.slice(&coff.mf), - }); - - // TODO: Is there a way to know if this is the last flush? We could skip doing this if so. - // TODO: Need to reindex with adaptor? - //try coff.export_table.entries.reIndexContext(gpa, ExportTable.Adapter{ .coff = coff }); -} - pub fn flush( coff: *Coff, arena: std.mem.Allocator, @@ -1961,9 +1896,6 @@ pub fn flush( _ = prog_node; while (try coff.idle(tid)) {} - coff.flushExports(tid) catch |err| - return coff.base.comp.link_diags.fail("linker failed to flush exports: {t}", .{err}); - // hack for stage2_x86_64 + coff const comp = coff.base.comp; if (comp.compiler_rt_dyn_lib) |crt_file| { @@ -2061,11 +1993,17 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } else coff.mf.update_prog_node.completeOne(); } + if (coff.export_table.pending_sort) { + coff.export_table.pending_sort = false; + coff.flushExportsSort(); + break :task; + } } if (coff.pending_uavs.count() > 0) return true; if (coff.globals.count() > coff.global_pending_index) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; if (coff.mf.updates.items.len > 0) return true; + if (coff.export_table.pending_sort) return true; return false; } @@ -2158,19 +2096,19 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const gpa = zcu.gpa; const gn = gmi.globalName(coff); - const target_endian = coff.targetEndian(); - const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); - const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { - _ => unreachable, - .PE32 => .{ 4, .@"4" }, - .@"PE32+" => .{ 8, .@"8" }, - }; - - const name = gn.name.toSlice(coff); if (gn.lib_name.toSlice(coff)) |lib_name| { + const name = gn.name.toSlice(coff); try coff.nodes.ensureUnusedCapacity(gpa, 4); try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + const target_endian = coff.targetEndian(); + const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); + const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { + _ => unreachable, + .PE32 => .{ 4, .@"4" }, + .@"PE32+" => .{ 8, .@"8" }, + }; + const gop = try coff.import_table.entries.getOrPutAdapted( gpa, lib_name, @@ -2315,47 +2253,6 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); - } else { - const entries_ctx = ExportTable.Adapter{ .coff = coff }; - const gop = try coff.export_table.entries.getOrPutAdapted( - gpa, - name, - entries_ctx, - ); - - if (!gop.found_existing) { - errdefer _ = coff.export_table.entries.pop(); - if (coff.export_table.entries.count() > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) - return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); - - const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size; - const new_name_table_size = name_index + name.len + 1; - if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) - return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); - - try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size); - - const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); - @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); - - gop.value_ptr.* = .{ - .name_index = @intCast(name_index), - .name_len = @intCast(name.len), - }; - - const si = gmi.symbol(coff); - const sym = si.get(coff); - - try coff.export_table.export_address_table_ni.resize( - &coff.mf, - gpa, - coff.export_table.entries.count() * @sizeOf(std.coff.ExportAddressTableEntry), - ); - const ea = &coff.exportAddressTableSlice()[gop.index]; - ea.export_or_forwarder_rva = sym.rva; - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportAddressTableEntry, &ea); - } } } @@ -2490,10 +2387,19 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva); coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable)); }, - .export_address_table => coff.targetStore( - &coff.exportDirectoryTable().export_address_table_rva, - coff.computeNodeRva(ni), - ), + .export_address_table => { + coff.export_table.export_address_table_si.flushMoved(coff); + + // These relocs are applied directly here instead of via the above flushMoved call as + // they are non-contiguous, and not tracked under export_address_table_si. + for (coff.export_table.entries.values()) |entry| + entry.export_address_table_ri.get(coff).apply(coff); + + coff.targetStore( + &coff.exportDirectoryTable().export_address_table_rva, + coff.computeNodeRva(ni), + ); + }, .export_name_pointer_table => coff.targetStore( &coff.exportDirectoryTable().name_pointer_table_rva, coff.computeNodeRva(ni), @@ -2503,8 +2409,18 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.computeNodeRva(ni), ), .export_name_table => { - // .export_name_pointer_table entries are updated in flush - log.warn("flushMoved export_name_table unhandled", .{}); + const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni); + for ( + coff.exportNamePointerTableSlice(), + coff.exportOrdinalTableSlice(), + ) |*np, target_ord| { + const ord: ExportTable.Ordinal = @enumFromInt(coff.targetLoad(&target_ord.unbiased_ordinal)); + const entry = ord.get(coff); + coff.targetStore( + &np.name_rva, + @intCast(name_table_rva + entry.name_index), + ); + } }, inline .pseudo_section, .object_section, @@ -2571,6 +2487,40 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { .global, .nav, .uav, .lazy_code, .lazy_const_data => {}, } } + +fn flushExportsSort(coff: *Coff) void { + const Context = struct { + coff: *Coff, + np: []std.coff.ExportNamePointerTableEntry, + ord: []std.coff.ExportOrdinalTableEntry, + entries: []ExportTable.Entry, + nt: []const u8, + + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)]; + const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)]; + return std.mem.lessThan( + u8, + ctx.nt[lhs_entry.name_index..][0..lhs_entry.name_len], + ctx.nt[rhs_entry.name_index..][0..rhs_entry.name_len], + ); + } + + pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { + std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]); + std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]); + } + }; + + std.sort.pdqContext(0, coff.export_table.entries.count(), Context{ + .coff = coff, + .np = coff.exportNamePointerTableSlice(), + .ord = coff.exportOrdinalTableSlice(), + .entries = coff.export_table.entries.values(), + .nt = coff.export_table.name_table_ni.slice(&coff.mf), + }); +} + fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void { var rva = start_rva; for ( @@ -2596,6 +2546,17 @@ pub fn updateExports( pt: Zcu.PerThread, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, +) !void { + return coff.updateExportsInner(pt, exported, export_indices) catch |err| switch (err) { + error.OutOfMemory => error.OutOfMemory, + else => |e| coff.base.comp.link_diags.fail("updateExports failed {t}", .{e}) catch error.AnalysisFail, + }; +} +fn updateExportsInner( + coff: *Coff, + pt: Zcu.PerThread, + exported: Zcu.Exported, + export_indices: []const Zcu.Export.Index, ) !void { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -2622,7 +2583,8 @@ pub fn updateExports( const exported_sym = exported_si.get(coff); for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); - const export_si = try coff.globalSymbol(@"export".opts.name.toSlice(ip), null); + const name = @"export".opts.name.toSlice(ip); + const export_si = try coff.globalSymbol(name, null); const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; @@ -2637,6 +2599,90 @@ pub fn updateExports( if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); } + + const entries_ctx = ExportTable.Adapter{ .coff = coff }; + const gop = try coff.export_table.entries.getOrPutAdapted( + gpa, + name, + entries_ctx, + ); + + if (!gop.found_existing) { + errdefer _ = coff.export_table.entries.pop(); + + const export_count = coff.export_table.entries.count(); + if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) + return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); + + const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size; + const new_name_table_size = name_index + name.len + 1; + if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) + return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); + + try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size); + + const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); + @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); + + // If the new name sorts after the current tail of the sorted list, we don't need to re-sort + const ordinal_table_slice = coff.exportOrdinalTableSlice(); + if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) { + const tail_index: ExportTable.Ordinal = + @enumFromInt(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal); + const tail_entry = tail_index.get(coff); + const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len]; + coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name); + } + + const edt = coff.exportDirectoryTable(); + coff.targetStore(&edt.number_of_names, @intCast(export_count)); + edt.number_of_entries = edt.number_of_names; + + try coff.export_table.export_address_table_si.node(coff).resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportAddressTableEntry), + ); + + try coff.export_table.name_pointer_table_ni.resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportNamePointerTableEntry), + ); + + try coff.export_table.ordinal_table_ni.resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportOrdinalTableEntry), + ); + + coff.targetStore( + &coff.exportNamePointerTableSlice()[gop.index].name_rva, + @intCast(coff.computeNodeRva(coff.export_table.name_table_ni) + name_index), + ); + coff.targetStore( + &coff.exportOrdinalTableSlice()[gop.index].unbiased_ordinal, + @intCast(gop.index), + ); + + gop.value_ptr.* = .{ + .name_index = @intCast(name_index), + .name_len = @intCast(name.len), + .export_address_table_ri = @enumFromInt(coff.relocs.items.len), + }; + + try coff.addReloc( + coff.export_table.export_address_table_si, + @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index), + export_si, + 0, + .{ .AMD64 = .ADDR32NB }, + ); + } else { + const reloc = gop.value_ptr.*.export_address_table_ri.get(coff); + reloc.target = export_si; + export_si.applyTargetRelocs(coff); + } } } -- 2.54.0 From 2b48ec1e720b1def48c0a89b35e8f60f3edb38be Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:33 -0400 Subject: [PATCH 03/94] - Only add the .edata section for images - Write the image name into the export directory table --- src/link/Coff.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 52d12a93cb7df173078b71909bb11bc21ea56fe5..a440ff68e9e1659d39b18df77927501a53d50910 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -789,6 +789,7 @@ fn create( minor_subsystem_version, magic, section_align, + std.fs.path.basename(path.sub_path), ); return coff; } @@ -823,6 +824,7 @@ fn initHeaders( minor_subsystem_version: u16, magic: std.coff.OptionalHeader.Magic, section_align: std.mem.Alignment, + file_name: []const u8, ) !void { const comp = coff.base.comp; const gpa = comp.gpa; @@ -1063,27 +1065,27 @@ fn initHeaders( ); coff.nodes.appendAssumeCapacity(.import_directory_table); - { - // TODO: Could create this lazily when processing the first export instead? + if (is_image) { const edata_section_ni = (try coff.pseudoSectionMapIndex( .@".edata", .of(std.coff.ExportDirectoryTable), .{ .read = true }, )).symbol(coff).node(coff); - const name = "TODO_NAME.dll"; - const name_index = @sizeOf(std.coff.ExportDirectoryTable); coff.export_table.ni = try coff.mf.addLastChildNode( gpa, edata_section_ni, .{ - .size = @sizeOf(std.coff.ExportDirectoryTable) + name.len + 1, + .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1, .alignment = .of(std.coff.ExportDirectoryTable), .fixed = true, .moved = true, }, ); - @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); + + const name_index = @sizeOf(std.coff.ExportDirectoryTable); + @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0..file_name.len], file_name[0..file_name.len]); + @memset(coff.export_table.ni.slice(&coff.mf)[name_index + file_name.len ..], 0); const export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ .alignment = .of(u32), @@ -2600,6 +2602,8 @@ fn updateExportsInner( std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); } + if (coff.export_table.ni == .none) continue; + const entries_ctx = ExportTable.Adapter{ .coff = coff }; const gop = try coff.export_table.entries.getOrPutAdapted( gpa, -- 2.54.0 From 01db3d49535d59c70de042c60ed524bca72e31f2 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 04/94] Coff: add support for outputing implibs This initial implementation used the existing mingw/implib functionality, and so builds the implib completely during flush(). The intended solution for this would be to build the implib with a MappedFile, similar to the main linker output. Added two new permutations to test/standalone/shared_library that exercise the `!use_llvm` path. --- src/link/Coff.zig | 89 +++++++++++++++++++++++- test/standalone/shared_library/build.zig | 13 ++-- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a440ff68e9e1659d39b18df77927501a53d50910..fba27bd785bddfc90d7ae74adf8f52272a343296 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -17,6 +17,8 @@ const target_util = @import("../target.zig"); const Type = @import("../Type.zig"); const Value = @import("../Value.zig"); const Zcu = @import("../Zcu.zig"); +const ModuleDefinition = @import("../libs/mingw/def.zig").ModuleDefinition; +const implib = @import("../libs/mingw/implib.zig"); base: link.File, mf: MappedFile, @@ -287,6 +289,7 @@ pub const ExportTable = struct { pending_sort: bool = false, pub const Entry = struct { + si: Symbol.Index, name_index: u32, name_len: u32, export_address_table_ri: Reloc.Index, @@ -1888,6 +1891,81 @@ pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void { }; } +fn flushImplib( + coff: *Coff, + implib_file: []const u8, +) !void { + // Emitting implibs is only valid for images + assert(coff.export_table.ni != .none); + + const comp = coff.base.comp; + const gpa = comp.gpa; + const io = comp.io; + + const image_name = std.mem.sliceTo( + coff.export_table.ni.slice(&coff.mf)[@sizeOf(std.coff.ExportDirectoryTable)..], + 0, + ); + const machine_type = coff.targetLoad(&coff.headerPtr().machine); + const members = members: { + const def_arena: std.heap.ArenaAllocator = .init(gpa); + var def: ModuleDefinition = .{ + .name = image_name, + .arena = def_arena, + .type = .mingw, + }; + defer def.deinit(); + + try def.exports.ensureUnusedCapacity( + def.arena.allocator(), + coff.export_table.entries.count(), + ); + + const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); + for (coff.export_table.entries.values(), 0..) |entry, ord| { + const name = name_table_slice[entry.name_index..][0..entry.name_len]; + const section_number = entry.si.get(coff).section_number; + const import_type: std.coff.ImportType = switch (section_number.symbol(coff)) { + .data, .rdata => .DATA, + .text => .CODE, + else => return comp.link_diags.fail( + "unsupported section for export '{s}': {s}", + .{ name, §ion_number.header(coff).name }, + ), + }; + + def.exports.appendAssumeCapacity(.{ + .name = name, + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = @intCast(ord), + .type = import_type, + .private = false, + }); + } + + def.fixupForImportLibraryGeneration(machine_type); + break :members try implib.getMembers(gpa, def, machine_type); + }; + defer members.deinit(); + + const lib_sub_path = try std.fs.path.join(gpa, &.{ + std.fs.path.dirname(coff.base.emit.sub_path) orelse "", + implib_file, + }); + defer gpa.free(lib_sub_path); + + const lib_final_file = try coff.base.emit.root_dir.handle.createFile(io, lib_sub_path, .{ .truncate = true }); + defer lib_final_file.close(io); + var buffer: [1024]u8 = undefined; + var file_writer = lib_final_file.writer(io, &buffer); + try implib.writeCoffArchive(gpa, &file_writer.interface, members); + try file_writer.interface.flush(); +} + pub fn flush( coff: *Coff, arena: std.mem.Allocator, @@ -1898,11 +1976,18 @@ pub fn flush( _ = prog_node; while (try coff.idle(tid)) {} + const comp = coff.base.comp; + + // Implib generation should instead be done via building a MappedFile progressively + if (comp.emit_implib) |implib_file| + coff.flushImplib(implib_file) catch |err| + return comp.link_diags.fail("flushing implib '{s}' failed: {t}", .{ implib_file, err }); + // hack for stage2_x86_64 + coff - const comp = coff.base.comp; if (comp.compiler_rt_dyn_lib) |crt_file| { const gpa = comp.gpa; const io = comp.io; + const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{ std.fs.path.dirname(coff.base.emit.sub_path) orelse "", std.fs.path.basename(crt_file.full_object_path.sub_path), @@ -2670,6 +2755,7 @@ fn updateExportsInner( ); gop.value_ptr.* = .{ + .si = export_si, .name_index = @intCast(name_index), .name_len = @intCast(name.len), .export_address_table_ri = @enumFromInt(coff.relocs.items.len), @@ -2683,6 +2769,7 @@ fn updateExportsInner( .{ .AMD64 = .ADDR32NB }, ); } else { + gop.value_ptr.si = export_si; const reloc = gop.value_ptr.*.export_address_table_ri.get(coff); reloc.target = export_si; export_si.applyTargetRelocs(coff); diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 444f5fd7bc7d2415a309958671b5c565383f52cd..7d9c2e7f53f6e3ba26497650ced048f0e41c1455 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -7,11 +7,15 @@ pub fn build(b: *std.Build) void { const optimize: std.builtin.OptimizeMode = .Debug; const target = b.standardTargetOptions(.{}); - const exe_names: []const []const u8 = &.{ "test", "test-dync" }; - const lib_names: []const []const u8 = &.{ "mathtest", "mathtest-dync" }; - const lib_link_libc: []const bool = &.{ false, true }; + const exe_names: []const []const u8 = &.{ "test", "test-dync", "test-no-llvm", "test-no-llvm-dync" }; + const lib_names: []const []const u8 = &.{ "mathtest", "mathtest-dync", "mathtest-no-llvm", "mathtest-no-llvm-dync" }; + const lib_link_libc: []const bool = &.{ false, true, false, true }; + const lib_use_llvm: []const bool = &.{ true, true, false, false }; + + for (exe_names, lib_names, lib_link_libc, lib_use_llvm) |exe_name, lib_name, dyn_libc, use_llvm| { + if (target.result.os.tag == .windows and target.result.abi == .gnu and dyn_libc and !use_llvm) + continue; // TODO: sub-compilation of compiler_rt failed (failed to link with LLD: LibCInstallationNotAvailable) - for (exe_names, lib_names, lib_link_libc) |exe_name, lib_name, dyn_libc| { const lib = b.addLibrary(.{ .linkage = .dynamic, .name = lib_name, @@ -22,6 +26,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = dyn_libc, }), + .use_llvm = use_llvm, }); const exe = b.addExecutable(.{ -- 2.54.0 From e12274f2bcae4a9aebac788bf57988e4fdf7ba10 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 05/94] - Skip the !use_llvm shared library tests on faiiling platforms - Outputting correct linker members --- lib/std/coff.zig | 27 + lib/std/start.zig | 2 +- src/crash_report.zig | 28 + src/link.zig | 34 +- src/link/Coff.zig | 956 ++++++++++++++++++++--- src/link/Elf2.zig | 14 +- src/link/MappedFile.zig | 7 + test/standalone/shared_library/build.zig | 12 +- 8 files changed, 939 insertions(+), 141 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 17cef8d412e85fc0445be733d6d4d6ef92adb69f..742f9d0a9af8537f571a236fa9aec5ea31bb536d 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -1963,3 +1963,30 @@ pub const IMAGE = struct { }; }; }; + +pub const ArchiveMemberHeader = extern struct { + /// Left-justified '/' terminated member name + name: [16]u8, + /// Left-justified ASCII decimal: seconds since January 1st, 1970 + date: [12]u8, + /// Left-justified ASCII decimal: user id + user_id: [6]u8, + /// Left-justified ASCII decimal: group id + group_id: [6]u8, + /// Left-justified ASCII octal: file mode + file_mode: [8]u8, + /// Left-justified ASCII decimal: size of the member following this header, + /// not including the size of this header. + size: [10]u8, + /// The literal string '`\n' + end_of_header: [2]u8, +}; + +pub const FirstLinkerMemberHeader = extern struct { + /// Big-endian symbol count + number_of_symbols: u32, +}; + +pub const SecondLinkerMemberHeader = extern struct { + number_of_members: u32, +}; diff --git a/lib/std/start.zig b/lib/std/start.zig index d779042189bf3eaeb8b7e41e5dd1fa7bb53f3491..68785b47aed7f36f2bf9dcea941eb12657c7c203 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -93,7 +93,7 @@ fn DllMainCRTStartup( fdwReason: std.os.windows.DWORD, lpReserved: std.os.windows.LPVOID, ) callconv(.winapi) std.os.windows.BOOL { - if (!builtin.single_threaded and !builtin.link_libc) { + if (!builtin.single_threaded) { _ = @import("os/windows/tls.zig"); } diff --git a/src/crash_report.zig b/src/crash_report.zig index b177af3bb6033ffc4e10e15a8aaf7c7e74c0bf03..6bdfd3f4751d0ddd0f28acbbbca1f6e1c9faf592 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -84,6 +84,25 @@ pub const CodegenFunc = if (enabled) struct { pub fn stop(_: InternPool.Index) void {} }; +pub const LinkerOp = if (enabled) struct { + lf: *link.File, + tid: Zcu.PerThread.Id, + threadlocal var current: ?LinkerOp = null; + pub fn start(lf: *link.File, tid: Zcu.PerThread.Id) void { + std.debug.assert(current == null); + current = .{ .lf = lf, .tid = tid }; + } + pub fn stop(lf: *link.File, tid: Zcu.PerThread.Id) void { + std.debug.assert(current.?.lf == lf and current.?.tid == tid); + current = null; + } +} else struct { + const current: ?noreturn = null; + // Dummy implementation + pub fn start(_: *link.File, _: Zcu.PerThread.Id) void {} + pub fn stop(_: *link.File, _: Zcu.PerThread.Id) void {} +}; + fn dumpCrashContext() Io.Writer.Error!void { const S = struct { /// In the case of recursive panics or segfaults, don't print the context for a second time. @@ -111,6 +130,14 @@ fn dumpCrashContext() Io.Writer.Error!void { try w.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); } else if (AnalyzeBody.current) |anal| { try dumpCrashContextSema(anal, w, &S.crash_heap); + } 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"); + } else { + try w.print("(build with -Dlink-snapshot to dump linker state)", .{}); + } } else { try w.writeAll("(no context)\n\n"); } @@ -185,6 +212,7 @@ const Zir = std.zig.Zir; const Sema = @import("Sema.zig"); const Zcu = @import("Zcu.zig"); +const link = @import("link.zig"); const InternPool = @import("InternPool.zig"); const dev = @import("dev.zig"); const print_zir = @import("print_zir.zig"); diff --git a/src/link.zig b/src/link.zig index 844eef3c407f3aecbc27b764a822808c5d1433e3..9cfd1d8a228dff54c1e4651d222acfd26f4863f5 100644 --- a/src/link.zig +++ b/src/link.zig @@ -25,6 +25,7 @@ const Package = @import("Package.zig"); const dev = @import("dev.zig"); const target_util = @import("target.zig"); const codegen = @import("codegen.zig"); +const crash_report = @import("crash_report.zig"); pub const aarch64 = @import("link/aarch64.zig"); pub const LdScript = @import("link/LdScript.zig"); @@ -790,6 +791,7 @@ pub const File = struct { assert(base.comp.zcu.?.llvm_object == null); const nav = pt.zcu.intern_pool.getNav(nav_index); assert(nav.resolved.?.value != .none); + switch (base.tag) { .lld => unreachable, .plan9 => unreachable, @@ -924,6 +926,9 @@ pub const File = struct { /// Commit pending changes and write headers. Takes into account final output mode. /// `arena` has the lifetime of the call to `Compilation.update`. pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) Error!void { + crash_report.LinkerOp.start(base, tid); + defer crash_report.LinkerOp.stop(base, tid); + const comp = base.comp; const io = comp.io; if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) { @@ -975,6 +980,10 @@ pub const File = struct { export_indices: []const Zcu.Export.Index, ) Error!void { assert(base.comp.zcu.?.llvm_object == null); + + crash_report.LinkerOp.start(base, pt.tid); + defer crash_report.LinkerOp.stop(base, pt.tid); + switch (base.tag) { .lld => unreachable, .plan9 => unreachable, @@ -1006,6 +1015,7 @@ pub const File = struct { /// Never called when LLVM is codegenning the ZCU. pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) Error!u64 { assert(base.comp.zcu.?.llvm_object == null); + switch (base.tag) { .lld => unreachable, .c => unreachable, @@ -1027,6 +1037,7 @@ pub const File = struct { decl_align: InternPool.Alignment, ) Error!SymbolId { assert(base.comp.zcu.?.llvm_object == null); + switch (base.tag) { .lld => unreachable, .c => unreachable, @@ -1043,6 +1054,7 @@ pub const File = struct { /// Never called when LLVM is codegenning the ZCU. pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) Error!u64 { assert(base.comp.zcu.?.llvm_object == null); + switch (base.tag) { .lld => unreachable, .c => unreachable, @@ -1063,6 +1075,7 @@ pub const File = struct { name: InternPool.NullTerminatedString, ) void { assert(base.comp.zcu.?.llvm_object == null); + switch (base.tag) { .lld => unreachable, .plan9 => unreachable, @@ -1077,6 +1090,24 @@ pub const File = struct { } } + pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !void { + if (!build_options.enable_link_snapshots) unreachable; + switch (base.tag) { + .elf, + .macho, + .c, + .wasm, + .spirv, + .plan9, + .lld, + => {}, + inline else => |tag| { + dev.check(tag.devFeature()); + return @as(*tag.Type(), @fieldParentPtr("base", base)).dump(w, tid); + }, + } + } + /// Opens a path as an object file and parses it into the linker. fn openLoadObject(base: *File, path: Path) anyerror!void { if (base.tag == .lld) return; @@ -1178,8 +1209,9 @@ pub const File = struct { pub fn loadInput(base: *File, input: Input) anyerror!void { if (base.tag == .lld) return; assert(!base.post_prelink); + switch (base.tag) { - inline .elf, .elf2, .wasm, .spirv => |tag| { + inline .coff2, .elf, .elf2, .wasm, .spirv => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input); }, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index fba27bd785bddfc90d7ae74adf8f52272a343296..d019efc3d8640b4a88e013157af8946b8d5f762b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -23,6 +23,11 @@ const implib = @import("../libs/mingw/implib.zig"); base: link.File, mf: MappedFile, nodes: std.MultiArrayList(Node), +members: std.ArrayList(Member), +pending_members: std.AutoArrayHashMapUnmanaged(Member.Index, void), +lib_string_table: std.ArrayList(String), +lib_string_len: u64, +long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, strings: std.HashMapUnmanaged( @@ -137,18 +142,27 @@ pub const msdos_stub: [120]u8 = .{ pub const Node = union(enum) { file, header, + /// Images and archives only. signature, + /// Archives only. + archive_member_header: Member.Index, + archive_member: Member.Index, + coff_header, + /// Image only optional_header, + /// Image only data_directories, section_table, image_section: Symbol.Index, + /// Only images contain imports import_directory_table, import_lookup_table: ImportTable.Index, import_address_table: ImportTable.Index, import_hint_name_table: ImportTable.Index, + /// Only images contain exports export_directory_table, export_address_table, export_name_pointer_table, @@ -163,6 +177,9 @@ pub const Node = union(enum) { lazy_code: LazyMapRef.Index(.code), lazy_const_data: LazyMapRef.Index(.const_data), + /// Takes the place of a known node index when that node is not present in the output + placeholder, + pub const PseudoSectionMapIndex = enum(u32) { _, @@ -262,6 +279,14 @@ pub const Node = union(enum) { file, header, signature, + first_linker_member_header, + first_linker_member, + second_linker_member_header, + second_linker_member, + longnames_member_header, + longnames_member, + zcu_member_header, + zcu_member, coff_header, optional_header, data_directories, @@ -279,8 +304,141 @@ pub const Node = union(enum) { } }; +pub const Member = struct { + kind: Kind, + header_ni: MappedFile.Node.Index, + content_ni: MappedFile.Node.Index, + // Maps symbols contained in this member to their index in the first linker member's symbol table + // TODO: This could contain information about the name string if we need + symbol_offsets: std.AutoArrayHashMapUnmanaged(Symbol.Index, u33), + + pub const Kind = enum { + first_linker, + second_linker, + longnames, + coff, + import, + }; + + pub const Index = enum(u16) { + first, + second, + longnames, + _, + + const known_count = @typeInfo(Index).@"enum".fields.len; + + pub fn get(member_index: Member.Index, coff: *Coff) *Member { + return &coff.members.items[@intFromEnum(member_index)]; + } + }; + + pub fn headerPtr(member: *Member, coff: *Coff) *std.coff.ArchiveMemberHeader { + return @ptrCast(@alignCast(member.header_ni.slice(&coff.mf))); + } + + pub fn initHeader(member: *Member, coff: *Coff, name: []const u8, timestamp: u32) !void { + const header = member.headerPtr(coff); + try storeHeaderName(coff, &header.name, name); + storeHeaderDecimalStr(&header.date, timestamp); + + // Matching the Microsoft behaviour of emitting blanks for these fields + header.user_id = @splat(' '); + header.group_id = @splat(' '); + + // file_mode is actually octal, but we only ever write 0 to it + storeHeaderDecimalStr(&header.file_mode, 0); + if (!member.content_ni.hasResized(&coff.mf)) + storeHeaderDecimalStr( + &header.size, + member.content_ni.location(&coff.mf).resolve(&coff.mf)[1], + ); + + @memcpy(&header.end_of_header, "`\n"); + } + + /// Sets `name` as the name field of this member's header, either directly (if it's short enough), + /// or by creating an entry in the longnames member and storing a reference to that entry. + pub fn storeHeaderName(coff: *Coff, field: *[16]u8, name: []const u8) !void { + if (name.len < field.len) { + @memcpy(field[0..name.len], name); + field[name.len] = '/'; + const padding = field.len - name.len - 1; + if (padding > 0) @memset(field[field.len - padding ..], ' '); + } else { + const gpa = coff.base.comp.gpa; + const entries_ctx = LongNamesTable.Adapter{ .coff = coff }; + const gop = try coff.long_names_table.entries.getOrPutAdapted( + gpa, + name, + entries_ctx, + ); + + if (!gop.found_existing) { + errdefer _ = coff.export_table.entries.pop(); + + _, const old_size = Node.known.longnames_member.location(&coff.mf).resolve(&coff.mf); + const new_size = old_size + name.len + 1; + assert(new_size < comptime try std.math.powi(u64, 10, field.len - 1)); + + try Node.known.longnames_member.resize(&coff.mf, gpa, new_size); + const name_table_slice = Node.known.longnames_member.slice(&coff.mf); + const name_slice = name_table_slice[old_size..][0 .. name.len + 1]; + @memcpy(name_slice[0..name.len], name); + name_slice[name.len] = 0; + + gop.value_ptr.* = .{ + .index = old_size, + .len = name.len, + }; + } + + field[0] = '/'; + storeHeaderDecimalStr(field[1..], gop.value_ptr.index); + } + } + + pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void { + const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array; + assert(array_info.child == u8); + assert(value < comptime try std.math.powi(u64, 10, array_info.len)); + _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{ + .width = array_info.len, + .alignment = .left, + .fill = ' ', + }); + } +}; + +pub const LongNamesTable = struct { + ni: MappedFile.Node.Index = .none, + entries: std.AutoArrayHashMapUnmanaged(void, Entry), + + pub const Entry = struct { + index: u64, + len: u64, + }; + + const Adapter = struct { + coff: *Coff, + + pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool { + assert(adapter.coff.isArchive()); // TODO: move to helper that uses this + const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf); + const rhs = adapter.coff.long_names_table.entries.values()[rhs_index]; + return std.mem.eql(u8, longnames_slice[rhs.index..][0..rhs.len], lhs_key); + } + + pub fn hash(_: Adapter, key: []const u8) u32 { + assert(std.mem.indexOfScalar(u8, key, 0) == null); + return std.array_hash_map.hashString(key); + } + }; +}; + pub const ExportTable = struct { ni: MappedFile.Node.Index, + export_directory_table_ni: MappedFile.Node.Index, export_address_table_si: Symbol.Index, name_pointer_table_ni: MappedFile.Node.Index, ordinal_table_ni: MappedFile.Node.Index, @@ -535,6 +693,13 @@ pub const Reloc = extern struct { const loc_slice = loc_sym.ni.slice(&coff.mf)[@intCast(reloc.offset)..]; const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend)); const target_endian = coff.targetEndian(); + + // TODO: Is this right? + const base = if (coff.isImage()) + coff.optionalHeaderField(.image_base) + else + 0; // should be offset within section - take target_rva - section_rva (but section is 0!) + switch (coff.targetLoad(&coff.headerPtr().machine)) { else => |machine| @panic(@tagName(machine)), .AMD64 => switch (reloc.type.AMD64) { @@ -543,13 +708,13 @@ pub const Reloc = extern struct { .ADDR64 => std.mem.writeInt( u64, loc_slice[0..8], - coff.optionalHeaderField(.image_base) + target_rva, + base + target_rva, target_endian, ), .ADDR32 => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(coff.optionalHeaderField(.image_base) + target_rva), + @intCast(base + target_rva), target_endian, ), .ADDR32NB => std.mem.writeInt( @@ -607,7 +772,7 @@ pub const Reloc = extern struct { .DIR16 => std.mem.writeInt( u16, loc_slice[0..2], - @intCast(coff.optionalHeaderField(.image_base) + target_rva), + @intCast(base + target_rva), target_endian, ), .REL16 => std.mem.writeInt( @@ -619,7 +784,7 @@ pub const Reloc = extern struct { .DIR32 => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(coff.optionalHeaderField(.image_base) + target_rva), + @intCast(base + target_rva), target_endian, ), .DIR32NB => std.mem.writeInt( @@ -691,14 +856,6 @@ fn create( assert(target.ofmt == .coff); if (target.cpu.arch.endian() != comptime targetEndian(undefined)) return error.UnsupportedCOFFArchitecture; - const is_image = switch (comp.config.output_mode) { - .Exe => true, - .Lib => switch (comp.config.link_mode) { - .static => false, - .dynamic => true, - }, - .Obj => false, - }; const machine = target.toCoffMachine(); const timestamp: u32 = 0; const major_subsystem_version = options.major_subsystem_version orelse 6; @@ -743,12 +900,20 @@ fn create( }, .mf = try .init(file, comp.gpa, io), .nodes = .empty, + .members = .empty, + .pending_members = .empty, + .lib_string_table = .empty, + .lib_string_len = 0, + .long_names_table = .{ + .entries = .empty, + }, .import_table = .{ .ni = .none, .entries = .empty, }, .export_table = .{ .ni = .none, + .export_directory_table_ni = .none, .export_address_table_si = .null, .name_pointer_table_ni = .none, .ordinal_table_ni = .none, @@ -785,7 +950,6 @@ fn create( } try coff.initHeaders( - is_image, machine, timestamp, major_subsystem_version, @@ -801,6 +965,7 @@ pub fn deinit(coff: *Coff) void { const gpa = coff.base.comp.gpa; coff.mf.deinit(gpa); coff.nodes.deinit(gpa); + coff.long_names_table.entries.deinit(gpa); coff.import_table.entries.deinit(gpa); coff.export_table.entries.deinit(gpa); coff.strings.deinit(gpa); @@ -818,9 +983,32 @@ pub fn deinit(coff: *Coff) void { coff.* = undefined; } +fn isImage(coff: *const Coff) bool { + const comp = coff.base.comp; + return switch (comp.config.output_mode) { + .Exe => true, + .Lib => switch (comp.config.link_mode) { + .static => false, + .dynamic => true, + }, + .Obj => false, + }; +} + +fn isArchive(coff: *const Coff) bool { + const comp = coff.base.comp; + return switch (comp.config.output_mode) { + .Exe => false, + .Lib => switch (comp.config.link_mode) { + .static => true, + .dynamic => false, + }, + .Obj => false, + }; +} + fn initHeaders( coff: *Coff, - is_image: bool, machine: std.coff.IMAGE.FILE.MACHINE, timestamp: u32, major_subsystem_version: u16, @@ -833,6 +1021,8 @@ fn initHeaders( const gpa = comp.gpa; const target_endian = coff.targetEndian(); const file_align: std.mem.Alignment = comptime .fromByteUnits(default_file_alignment); + const is_image = coff.isImage(); + const is_archive = coff.isArchive(); const optional_header_size: u16 = if (is_image) switch (magic) { _ => unreachable, @@ -843,33 +1033,106 @@ fn initHeaders( else 0; - const expected_nodes_len = Node.known_count + 12 + - @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2; + var expected_nodes_len: usize = Node.known_count; + if (comp.zcu != null) { + expected_nodes_len += 3; + if (is_image) expected_nodes_len += 9; + expected_nodes_len += @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2; + } + defer assert(coff.nodes.len == expected_nodes_len); + try coff.nodes.ensureTotalCapacity(gpa, expected_nodes_len); coff.nodes.appendAssumeCapacity(.file); const header_ni = Node.known.header; - assert(header_ni == try coff.mf.addOnlyChildNode(gpa, .root, .{ + assert(header_ni == try coff.mf.addOnlyChildNode(gpa, Node.known.file, .{ .alignment = coff.mf.flags.block_size, .fixed = true, })); coff.nodes.appendAssumeCapacity(.header); + const pe_signature = "PE\x00\x00"; + const archive_signature = "!\n"; + const signature_ni = Node.known.signature; - assert(signature_ni == try coff.mf.addOnlyChildNode(gpa, header_ni, .{ - .size = (if (is_image) msdos_stub.len else 0) + "PE\x00\x00".len, + assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image) header_ni else Node.known.file, .{ + .size = if (is_image) + msdos_stub.len + pe_signature.len + else if (is_archive) + archive_signature.len + else + 0, .alignment = .@"4", .fixed = true, })); coff.nodes.appendAssumeCapacity(.signature); - { - const signature_slice = signature_ni.slice(&coff.mf); - if (is_image) @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub); - @memcpy(signature_slice[signature_slice.len - 4 ..], "PE\x00\x00"); + + const signature_slice = signature_ni.slice(&coff.mf); + if (is_image) { + @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub); + @memcpy(signature_slice[signature_slice.len - pe_signature.len ..], pe_signature); + } else if (is_archive) { + @memcpy(signature_slice, archive_signature); } + const opt_zcu_coff_parent_ni = if (is_archive) parent: { + const initial_member_count = Member.Index.known_count + @intFromBool(comp.zcu != null); + try coff.members.ensureTotalCapacity(gpa, initial_member_count); + + assert(Member.Index.first == try coff.addMemberAssumeCapacity(.first_linker, @sizeOf(u32))); + coff.targetStore(coff.firstLinkerMemberNumSymbolsPtr(), 0); + + assert(Member.Index.second == try coff.addMemberAssumeCapacity(.second_linker, 2 * @sizeOf(u32))); + coff.targetStore(coff.secondLinkerMemberNumMembersPtr(), 0); + coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), 0); + + assert(Member.Index.longnames == try coff.addMemberAssumeCapacity(.longnames, 0)); + + const first_linker_member = Member.Index.first.get(coff); + const second_linker_member = Member.Index.second.get(coff); + const longnames_member = Member.Index.longnames.get(coff); + + try first_linker_member.initHeader(coff, "", timestamp); + try second_linker_member.initHeader(coff, "", timestamp); + try longnames_member.initHeader(coff, "/", timestamp); + + if (comp.zcu) |zcu| { + const zcu_mi = try coff.addMemberAssumeCapacity(.coff, @sizeOf(std.coff.Header)); + const zcu_member = zcu_mi.get(coff); + try zcu_member.initHeader(coff, zcu.main_mod.fully_qualified_name, timestamp); + + break :parent zcu_member.content_ni; + } + + assert(Node.known.zcu_member_header == try coff.mf.addLastChildNode(gpa, Node.known.file, .{})); + assert(Node.known.zcu_member == try coff.mf.addLastChildNode(gpa, Node.known.file, .{})); + coff.nodes.appendAssumeCapacity(.placeholder); + coff.nodes.appendAssumeCapacity(.placeholder); + + break :parent null; + } else parent: { + // TODO: Not ideal to have this many placeholder nodes - use two distinct `Node.known` types? + while (true) { + const placeholder_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{}); + coff.nodes.appendAssumeCapacity(.placeholder); + if (placeholder_ni == Node.known.zcu_member) break; + } + + break :parent if (comp.zcu != null) Node.known.header else null; + }; + + const zcu_coff_parent_ni = opt_zcu_coff_parent_ni orelse { + // If we're not generating any code, no more known nodes are used + while (coff.nodes.len < Node.known_count) { + _ = try coff.mf.addLastChildNode(gpa, Node.known.file, .{}); + coff.nodes.appendAssumeCapacity(.placeholder); + } + + return; + }; + const coff_header_ni = Node.known.coff_header; - assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{ + assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ .size = @sizeOf(std.coff.Header), .alignment = .@"4", .fixed = true, @@ -897,7 +1160,7 @@ fn initHeaders( } const optional_header_ni = Node.known.optional_header; - assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{ + assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ .size = optional_header_size, .alignment = .@"4", .fixed = true, @@ -1009,13 +1272,13 @@ fn initHeaders( } const data_directories_ni = Node.known.data_directories; - assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{ + assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ .size = data_directories_size, .alignment = .@"4", .fixed = true, })); coff.nodes.appendAssumeCapacity(.data_directories); - { + if (is_image) { const data_directories = coff.dataDirectorySlice(); @memset(data_directories, .{ .virtual_address = 0, .size = 0 }); if (target_endian != native_endian) std.mem.byteSwapAllFields( @@ -1025,7 +1288,7 @@ fn initHeaders( } const section_table_ni = Node.known.section_table; - assert(section_table_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{ + assert(section_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ .alignment = .@"4", .fixed = true, })); @@ -1057,43 +1320,45 @@ fn initHeaders( .MEM_READ = true, }) == .text); - coff.import_table.ni = try coff.mf.addLastChildNode( - gpa, - (try coff.objectSectionMapIndex( - .@".idata", - coff.mf.flags.block_size, - .{ .read = true }, - )).symbol(coff).node(coff), - .{ .alignment = .@"4", .moved = true }, - ); - coff.nodes.appendAssumeCapacity(.import_directory_table); - if (is_image) { - const edata_section_ni = (try coff.pseudoSectionMapIndex( + coff.import_table.ni = try coff.mf.addLastChildNode( + gpa, + (try coff.objectSectionMapIndex( + .@".idata", + coff.mf.flags.block_size, + .{ .read = true }, + )).symbol(coff).node(coff), + .{ .alignment = .@"4", .moved = true }, + ); + coff.nodes.appendAssumeCapacity(.import_directory_table); + + coff.export_table.ni = (try coff.pseudoSectionMapIndex( .@".edata", .of(std.coff.ExportDirectoryTable), .{ .read = true }, )).symbol(coff).node(coff); - coff.export_table.ni = try coff.mf.addLastChildNode( + coff.export_table.export_directory_table_ni = try coff.mf.addLastChildNode( gpa, - edata_section_ni, + coff.export_table.ni, .{ .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1, - .alignment = .of(std.coff.ExportDirectoryTable), - .fixed = true, .moved = true, + .fixed = true, }, ); + coff.nodes.appendAssumeCapacity(.export_directory_table); const name_index = @sizeOf(std.coff.ExportDirectoryTable); - @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0..file_name.len], file_name[0..file_name.len]); - @memset(coff.export_table.ni.slice(&coff.mf)[name_index + file_name.len ..], 0); + const table_slice = coff.export_table.export_directory_table_ni.slice(&coff.mf); + @memcpy(table_slice[name_index..][0..file_name.len], file_name[0..file_name.len]); + @memset(table_slice[name_index + file_name.len ..], 0); - const export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ - .alignment = .of(u32), + const export_address_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{ + .alignment = .of(std.coff.ExportAddressTableEntry), .moved = true, }); + coff.nodes.appendAssumeCapacity(.export_address_table); try coff.symbol_table.ensureUnusedCapacity(gpa, 1); coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity(); @@ -1103,25 +1368,24 @@ fn initHeaders( assert(export_address_table_sym.loc_relocs == .none); export_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); export_address_table_sym.section_number = - coff.getNode(edata_section_ni).pseudo_section.symbol(coff).get(coff).section_number; + coff.getNode(coff.export_table.ni).pseudo_section.symbol(coff).get(coff).section_number; - coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ - .alignment = .of(u32), + coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{ + .alignment = .of(std.coff.ExportNamePointerTableEntry), .moved = true, }); - coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ - .alignment = .of(u16), - .moved = true, - }); - coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ - .alignment = .of(u8), - .moved = true, - }); - - coff.nodes.appendAssumeCapacity(.export_directory_table); - coff.nodes.appendAssumeCapacity(.export_address_table); coff.nodes.appendAssumeCapacity(.export_name_pointer_table); + + coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{ + .alignment = .of(std.coff.ExportOrdinalTableEntry), + .moved = true, + }); coff.nodes.appendAssumeCapacity(.export_ordinal_table); + + coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{ + .alignment = .of(u8), + .moved = true, + }); coff.nodes.appendAssumeCapacity(.export_name_table); const export_directory_table = coff.exportDirectoryTable(); @@ -1145,11 +1409,9 @@ fn initHeaders( // While tls variables allocated at runtime are writable, the template itself is not if (comp.config.any_non_single_threaded) _ = try coff.objectSectionMapIndex( .@".tls$", - coff.mf.flags.block_size, + if (is_image) coff.mf.flags.block_size else .@"1", .{ .read = true }, ); - - assert(coff.nodes.len == expected_nodes_len); } pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { @@ -1181,11 +1443,14 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .file, .header, .signature, + .archive_member_header, + .archive_member, .coff_header, .optional_header, .data_directories, .section_table, .export_name_table, + .placeholder, => unreachable, .image_section => |si| si, .import_directory_table => break :parent_rva coff.targetLoad( @@ -1272,9 +1537,55 @@ fn targetStore(coff: *const Coff, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).poi } pub fn headerPtr(coff: *Coff) *std.coff.Header { + assert(coff.base.comp.zcu != null); return @ptrCast(@alignCast(Node.known.coff_header.slice(&coff.mf))); } +pub fn firstLinkerMemberNumSymbolsPtr(coff: *Coff) *u32 { + assert(coff.isArchive()); + return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf))); +} + +pub fn firstLinkerMemberOffsetsSlice(coff: *Coff) []u32 { + const len = std.mem.toNative(u32, coff.firstLinkerMemberNumSymbolsPtr().*, .big); + return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. len * @sizeOf(u32)])); +} + +pub fn secondLinkerMemberNumMembersPtr(coff: *Coff) *u32 { + assert(coff.isArchive()); + return @ptrCast(@alignCast(Node.known.second_linker_member.slice(&coff.mf))); +} + +pub fn secondLinkerMemberOffsetsSlice(coff: *Coff) []u32 { + const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); + return @ptrCast(@alignCast( + Node.known.second_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. num_members * @sizeOf(u32)], + )); +} + +pub fn secondLinkerMemberNumSymbolsPtr(coff: *Coff) *u32 { + const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); + return @ptrCast(@alignCast( + Node.known.second_linker_member.slice(&coff.mf)[(1 + num_members) * @sizeOf(u32) ..], + )); +} + +pub fn secondLinkerMemberIndicesSlice(coff: *Coff) []u16 { + const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); + const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr()); + return @ptrCast(@alignCast( + Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) ..][0 .. num_symbols * @sizeOf(u16)], + )); +} + +pub fn secondLinkerMemberStringsSlice(coff: *Coff) []u8 { + const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); + const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr()); + return @ptrCast(@alignCast( + Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) + num_symbols * @sizeOf(u16) ..], + )); +} + pub fn optionalHeaderStandardPtr(coff: *Coff) *std.coff.OptionalHeader { return @ptrCast(@alignCast( Node.known.optional_header.slice(&coff.mf)[0..@sizeOf(std.coff.OptionalHeader)], @@ -1286,6 +1597,7 @@ pub const OptionalHeaderPtr = union(std.coff.OptionalHeader.Magic) { @"PE32+": *std.coff.OptionalHeader.@"PE32+", }; pub fn optionalHeaderPtr(coff: *Coff) OptionalHeaderPtr { + assert(coff.isImage()); const slice = Node.known.optional_header.slice(&coff.mf); return switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) { _ => unreachable, @@ -1300,6 +1612,7 @@ pub fn optionalHeaderField( coff: *Coff, comptime field: std.meta.FieldEnum(std.coff.OptionalHeader.@"PE32+"), ) @FieldType(std.coff.OptionalHeader.@"PE32+", @tagName(field)) { + assert(coff.isImage()); return switch (coff.optionalHeaderPtr()) { inline else => |optional_header| coff.targetLoad(&@field(optional_header, @tagName(field))), }; @@ -1308,6 +1621,7 @@ pub fn optionalHeaderField( pub fn dataDirectorySlice( coff: *Coff, ) *[std.coff.IMAGE.DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory { + assert(coff.isImage()); return @ptrCast(@alignCast(Node.known.data_directories.slice(&coff.mf))); } pub fn dataDirectoryPtr( @@ -1322,6 +1636,7 @@ pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader { } pub fn importDirectoryTableSlice(coff: *Coff) []std.coff.ImportDirectoryEntry { + assert(coff.isImage()); return @ptrCast(@alignCast(coff.import_table.ni.slice(&coff.mf))); } pub fn importDirectoryEntryPtr( @@ -1332,10 +1647,13 @@ pub fn importDirectoryEntryPtr( } pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable { - return @ptrCast(@alignCast(coff.export_table.ni.slice(&coff.mf))); + return @ptrCast(@alignCast(coff.export_table.export_directory_table_ni.slice(&coff.mf))); } pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry { + const debug = coff.export_table.name_pointer_table_ni.slice(&coff.mf); + _ = debug; + return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf))); } @@ -1388,6 +1706,14 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { } pub fn globalSymbol(coff: *Coff, name: []const u8, lib_name: ?[]const u8) !Symbol.Index { + return (try getOrPutGlobalSymbol(coff, name, lib_name)).value_ptr.*; +} + +fn getOrPutGlobalSymbol( + coff: *Coff, + name: []const u8, + lib_name: ?[]const u8, +) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult { const gpa = coff.base.comp.gpa; try coff.symbol_table.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.globals.getOrPut(gpa, .{ @@ -1398,7 +1724,7 @@ pub fn globalSymbol(coff: *Coff, name: []const u8, lib_name: ?[]const u8) !Symbo sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); coff.synth_prog_node.increaseEstimatedTotalItems(1); } - return sym_gop.value_ptr.*; + return sym_gop; } fn navSection( @@ -1500,10 +1826,187 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. .I386 => .{ .I386 = .DIR32 }, }, ); - return coff.optionalHeaderField(.image_base) + target_si.get(coff).rva; + + var vaddr: u64 = target_si.get(coff).rva; + if (coff.isImage()) vaddr += coff.optionalHeaderField(.image_base); + return vaddr; +} + +/// Caller guarantees there is capacity for one member and two nodes +fn addMemberAssumeCapacity(coff: *Coff, kind: Member.Kind, size: usize) !Member.Index { + const comp = coff.base.comp; + const gpa = comp.gpa; + + // TODO: These two nodes could to be inside a movable node? Only if coff or import + + const header_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{ + .size = @sizeOf(std.coff.ArchiveMemberHeader), + .alignment = .@"2", + .fixed = true, + .moved = true, + }); + + const content_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{ + // The actual alignment required by the spec is 2, but to allow aligned access to + // the various COFF data structures in-place during linking we overalign + .alignment = switch (kind) { + .coff => .@"4", + else => .@"2", + }, + .size = size, + .resized = size > 0, + .fixed = true, + }); + + const mi: Member.Index = @enumFromInt(coff.members.items.len); + coff.members.appendAssumeCapacity(.{ + .kind = kind, + .header_ni = header_ni, + .content_ni = content_ni, + .symbol_offsets = .empty, + }); + + coff.nodes.appendAssumeCapacity(.{ .archive_member_header = mi }); + coff.nodes.appendAssumeCapacity(.{ .archive_member = mi }); + + switch (kind) { + .first_linker, .second_linker, .longnames => {}, + else => { + const new_num_members = coff.members.items.len - Member.Index.known_count; + coff.targetStore( + coff.secondLinkerMemberNumMembersPtr(), + @intCast(new_num_members), + ); + + const old_size = Node.known.second_linker_member.location(&coff.mf).resolve(&coff.mf)[1]; + const old_header_size = new_num_members * @sizeOf(u32); + const trailing_size = old_size - old_header_size; + try Node.known.second_linker_member.resize(&coff.mf, gpa, old_size + @sizeOf(u32)); + + const slice = Node.known.second_linker_member.slice(&coff.mf); + @memmove( + slice[old_header_size + @sizeOf(u32) ..][0..trailing_size], + slice[old_header_size..][0..trailing_size], + ); + + // Offset will be written by flushMoved on header_ni + }, + } + + switch (kind) { + .first_linker, + .longnames, + .import, + => {}, + .second_linker, + .coff, + => { + try coff.pending_members.ensureTotalCapacity( + gpa, + coff.pending_members.capacity() + 1, + ); + }, + } + + return mi; +} + +fn appendMemberSymbolString( + coff: *Coff, + strings_ni: MappedFile.Node.Index, + new_size: u64, + name: []const u8, + offset: u64, +) !void { + try strings_ni.resize(&coff.mf, coff.base.comp.gpa, new_size); + const name_slice = strings_ni.slice(&coff.mf)[offset..][0 .. name.len + 1]; + @memcpy(name_slice[0..name.len], name); + name_slice[name.len] = 0; +} + +fn addMemberSymbol( + coff: *Coff, + name: String, + mi: Member.Index, + si: Symbol.Index, +) !void { + const gpa = coff.base.comp.gpa; + const member = mi.get(coff); + assert(member.kind == .coff); + + const gop = try member.symbol_offsets.getOrPut(gpa, si); + if (gop.found_existing) return; + + // TODO: Detect duplicate names (ie. a name used by a symbol in another member, not the zcu since those already go through globals) + + const symbol_index = blk: { + const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr(); + const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big); + num_symbols_ptr.* = std.mem.nativeTo(u32, num_symbols + 1, .big); + break :blk num_symbols; + }; + + gop.value_ptr.* = symbol_index; + const name_slice = name.toSlice(coff); + + // Linker member fields are not modeled as nodes because MappedFile + // can't guarantee that they will be tightly packed after resizing + + const new_string_table_size = coff.lib_string_len + name_slice.len + 1; + defer coff.lib_string_len = new_string_table_size; + + { + const old_header_size = @sizeOf(u32) + symbol_index * @sizeOf(u32); + const new_header_size = old_header_size + @sizeOf(u32); + try Node.known.first_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size); + + const slice = Node.known.first_linker_member.slice(&coff.mf); + @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); + @memcpy(slice[new_header_size + coff.lib_string_len ..][0 .. name_slice.len + 1], name_slice[0 .. name_slice.len + 1]); + + // New offset entry is written in flushMember + } + + { + const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); + const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + symbol_index * @sizeOf(u16); + const new_header_size = old_header_size + @sizeOf(u16); + try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size); + + const needs_sort = if (coff.lib_string_table.items.len > 0) + std.mem.lessThan( + u8, + name_slice, + coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff), + ) + else + false; + + try coff.lib_string_table.append(gpa, name); + + const slice = Node.known.second_linker_member.slice(&coff.mf); + const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..])); + coff.targetStore(num_symbols_ptr, symbol_index + 1); + + if (needs_sort) { + // The entire string table is rebuilt in flushMember after sorting + coff.pending_members.putAssumeCapacity(Member.Index.second, {}); + } else { + @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); + @memcpy(slice[new_header_size + coff.lib_string_len ..][0 .. name_slice.len + 1], name_slice[0 .. name_slice.len + 1]); + } + + // Indices in this table are 1-based + const index_ptr: *u16 = @ptrCast(@alignCast(slice[old_header_size..])); + coff.targetStore(index_ptr, @intCast(@intFromEnum(mi) - Member.Index.known_count + 1)); + } + + coff.pending_members.putAssumeCapacity(mi, {}); } fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index { + assert(coff.base.comp.zcu != null); + const gpa = coff.base.comp.gpa; try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.image_section_table.ensureUnusedCapacity(gpa, 1); @@ -1518,21 +2021,34 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags gpa, @sizeOf(std.coff.SectionHeader) * section_table_len, ); - const ni = try coff.mf.addLastChildNode(gpa, .root, .{ - .alignment = coff.mf.flags.block_size, + + const parent_ni, const alignment = if (coff.isArchive()) + .{ Node.known.zcu_member, .@"1" } + else + .{ Node.known.file, coff.mf.flags.block_size }; + + const ni = try coff.mf.addLastChildNode(gpa, parent_ni, .{ + .alignment = alignment, .moved = true, .bubbles_moved = false, }); + const si = coff.addSymbolAssumeCapacity(); coff.image_section_table.appendAssumeCapacity(si); coff.nodes.appendAssumeCapacity(.{ .image_section = si }); const section_table = coff.sectionTableSlice(); - const virtual_size = coff.optionalHeaderField(.section_alignment); - const rva: u32 = switch (section_index) { - 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]), - else => coff.image_section_table.items[section_index - 1].get(coff).rva + - coff.targetLoad(§ion_table[section_index - 1].virtual_size), - }; + + const virtual_size, const rva = if (coff.isImage()) block: { + const virtual_size = coff.optionalHeaderField(.section_alignment); + const rva: u32 = switch (section_index) { + 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]), + else => coff.image_section_table.items[section_index - 1].get(coff).rva + + coff.targetLoad(§ion_table[section_index - 1].virtual_size), + }; + + break :block .{ virtual_size, rva }; + } else .{ 0, 0 }; + { const sym = si.get(coff); sym.ni = ni; @@ -1556,12 +2072,16 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags @memset(section.name[name.len..], 0); if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.SectionHeader, section); - switch (coff.optionalHeaderPtr()) { - inline else => |optional_header| coff.targetStore( - &optional_header.size_of_image, - @intCast(rva + virtual_size), - ), + + if (coff.isImage()) { + switch (coff.optionalHeaderPtr()) { + inline else => |optional_header| coff.targetStore( + &optional_header.size_of_image, + @intCast(rva + virtual_size), + ), + } } + return si; } @@ -1686,6 +2206,16 @@ pub fn addReloc( target.target_relocs = ri; } +pub fn loadInput(coff: *Coff, input: link.Input) void { + _ = coff; + switch (input) { + .dso_exact => unreachable, + inline else => |i, tag| { + log.debug("loadInput({s}: {f})", .{ @tagName(tag), i.path.fmtEscapeString() }); + }, + } +} + pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { _ = coff; _ = prog_node; @@ -1976,6 +2506,11 @@ pub fn flush( _ = prog_node; while (try coff.idle(tid)) {} + // TODO: Second linker member symbol tables are built here + if (isArchive(coff)) { + //Member.Index.second.get(coff).content_ni; + } + const comp = coff.base.comp; // Implib generation should instead be done via building a MappedFile progressively @@ -1985,8 +2520,8 @@ pub fn flush( // hack for stage2_x86_64 + coff if (comp.compiler_rt_dyn_lib) |crt_file| { - const gpa = comp.gpa; const io = comp.io; + const gpa = comp.gpa; const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{ std.fs.path.dirname(coff.base.emit.sub_path) orelse "", @@ -2002,6 +2537,14 @@ pub fn flush( .{}, ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err }); } + + coff.mf.flush() catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), + }; + + coff.dumpStderr(tid) catch |err| + return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err}); } pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { @@ -2080,7 +2623,13 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } else coff.mf.update_prog_node.completeOne(); } + while (coff.pending_members.pop()) |pending_mi| { + // TODO: Prog node + try coff.flushMember(pending_mi.key); + break :task; + } if (coff.export_table.pending_sort) { + // TODO: Prog node coff.export_table.pending_sort = false; coff.flushExportsSort(); break :task; @@ -2090,6 +2639,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.globals.count() > coff.global_pending_index) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; if (coff.mf.updates.items.len > 0) return true; + if (coff.pending_members.count() > 0) return true; if (coff.export_table.pending_sort) return true; return false; } @@ -2183,6 +2733,10 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const gpa = zcu.gpa; const gn = gmi.globalName(coff); + // TODO: We still need to emit a reloc for the __imp_Name symbol? + + if (!coff.isImage()) return; + if (gn.lib_name.toSlice(coff)) |lib_name| { const name = gn.name.toSlice(coff); try coff.nodes.ensureUnusedCapacity(gpa, 4); @@ -2394,19 +2948,44 @@ 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))}); switch (coff.getNode(ni)) { .file, .header, .signature, + => unreachable, .coff_header, .optional_header, .data_directories, .section_table, - => unreachable, - .image_section => |si| return coff.targetStore( - &si.get(coff).section_number.header(coff).pointer_to_raw_data, - @intCast(ni.fileLocation(&coff.mf, false).offset), - ), + .placeholder, + => if (!coff.isArchive()) unreachable, + .archive_member_header => |mi| { + const member = mi.get(coff); + switch (member.kind) { + .first_linker, .second_linker, .longnames => {}, + else => coff.targetStore( + &coff.secondLinkerMemberOffsetsSlice()[@intFromEnum(mi) - Member.Index.known_count], + @intCast(ni.fileLocation(&coff.mf, false).offset), + ), + } + + if (member.kind == .coff) + try coff.pending_members.put(coff.base.comp.gpa, mi, {}); + }, + .archive_member, + => {}, + .image_section => |si| { + const file_offset = if (isArchive(coff)) + si.get(coff).ni.location(&coff.mf).resolve(&coff.mf)[0] + else + ni.fileLocation(&coff.mf, false).offset; + + return coff.targetStore( + &si.get(coff).section_number.header(coff).pointer_to_raw_data, + @intCast(file_offset), + ); + }, .import_directory_table => coff.targetStore( &coff.dataDirectoryPtr(.IMPORT).virtual_address, coff.computeNodeRva(ni), @@ -2523,32 +3102,69 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { _, const size = ni.location(&coff.mf).resolve(&coff.mf); + log.debug("flushResized({s}, 0x{x})", .{ @tagName(coff.getNode(ni)), size }); + switch (coff.getNode(ni)) { - .file => {}, + .file => { + if (coff.isArchive() and coff.members.items.len > 0) { + const last_member = coff.members.items[coff.members.items.len - 1]; + assert(Node.known.file.reverseChildren(&coff.mf).ni == last_member.content_ni); + try coff.flushResized(last_member.content_ni); + } + }, .header => { - switch (coff.optionalHeaderPtr()) { - inline else => |optional_header| coff.targetStore( - &optional_header.size_of_headers, - @intCast(size), - ), + if (coff.isImage()) { + switch (coff.optionalHeaderPtr()) { + inline else => |optional_header| coff.targetStore( + &optional_header.size_of_headers, + @intCast(size), + ), + } + + if (size > coff.image_section_table.items[0].get(coff).rva) try coff.virtualSlide( + 0, + std.mem.alignForward( + u32, + @intCast(size * 4), + coff.optionalHeaderField(.section_alignment), + ), + ); } - if (size > coff.image_section_table.items[0].get(coff).rva) try coff.virtualSlide( - 0, - std.mem.alignForward( - u32, - @intCast(size * 4), - coff.optionalHeaderField(.section_alignment), - ), - ); }, - .signature, .coff_header, .optional_header, .data_directories => unreachable, + .signature, + .archive_member_header, + => unreachable, + .archive_member => |mi| { + const content_ni = mi.get(coff).content_ni; + const next_ni = content_ni.next(&coff.mf); + const offset, _ = content_ni.location(&coff.mf).resolve(&coff.mf); + const next_offset = switch (next_ni) { + .none => offset: { + assert(content_ni.parent(&coff.mf) == Node.known.file); + // This must take into account the final file size. If there are trailing + // bytes, they will be expected to contain another valid member header + break :offset coff.mf.memory_map.memory.len; + }, + else => offset: { + assert(coff.getNode(next_ni) == .archive_member_header); + break :offset next_ni.location(&coff.mf).resolve(&coff.mf)[0]; + }, + }; + + // Not inserting IMAGE_ARCHIVE_PAD `\n` byte here, because we are expanding to full size + Member.storeHeaderDecimalStr(&mi.get(coff).headerPtr(coff).size, next_offset - offset); + }, + .coff_header, + .optional_header, + .data_directories, + => unreachable, .section_table => {}, .image_section => |si| { const sym = si.get(coff); const section_index = sym.section_number.toIndex(); const section = &coff.sectionTableSlice()[section_index]; coff.targetStore(§ion.size_of_raw_data, @intCast(size)); - if (size > coff.targetLoad(§ion.virtual_size)) { + if (coff.isImage() and size > coff.targetLoad(§ion.virtual_size)) { const virtual_size = std.mem.alignForward( u32, @intCast(size * 4), @@ -2562,16 +3178,87 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { &coff.dataDirectoryPtr(.IMPORT).size, @intCast(size), ), - .import_lookup_table, .import_address_table, .import_hint_name_table => {}, - .export_directory_table => coff.targetStore( - &coff.dataDirectoryPtr(.EXPORT).size, - @intCast(size), - ), - .export_address_table, .export_name_pointer_table, .export_ordinal_table, .export_name_table => {}, + .import_lookup_table, + .import_address_table, + .import_hint_name_table, + => {}, + .export_directory_table => unreachable, + .export_address_table, + .export_name_pointer_table, + .export_ordinal_table, + .export_name_table, + => {}, inline .pseudo_section, .object_section, - => |smi| smi.symbol(coff).get(coff).size = @intCast(size), - .global, .nav, .uav, .lazy_code, .lazy_const_data => {}, + => |smi, tag| { + if (tag == .pseudo_section and smi.name(coff) == .@".edata") { + coff.targetStore( + &coff.dataDirectoryPtr(.EXPORT).size, + @intCast(size), + ); + } + + smi.symbol(coff).get(coff).size = @intCast(size); + }, + .global, + .nav, + .uav, + .lazy_code, + .lazy_const_data, + => {}, + .placeholder => unreachable, + } +} + +fn flushMember(coff: *Coff, mi: Member.Index) !void { + const member = mi.get(coff); + switch (member.kind) { + .first_linker, + .longnames, + .import, + => unreachable, + .second_linker => { + const Context = struct { + coff: *Coff, + indices: []u16, + strings: []String, + + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + return std.mem.lessThan( + u8, + ctx.strings[lhs].toSlice(ctx.coff), + ctx.strings[rhs].toSlice(ctx.coff), + ); + } + + pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { + std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]); + std.mem.swap(String, &ctx.strings[lhs], &ctx.strings[rhs]); + } + }; + + std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{ + .coff = coff, + .indices = coff.secondLinkerMemberIndicesSlice(), + .strings = coff.lib_string_table.items, + }); + + var offset: u64 = 0; + + var string_table = coff.secondLinkerMemberStringsSlice(); + for (coff.lib_string_table.items) |string| { + const str = string.toSlice(coff); + @memcpy(string_table[offset..][0..str.len], str); + string_table[offset + str.len] = 0; + offset += str.len + 1; + } + }, + .coff => { + const file_offset: u32 = @intCast(member.header_ni.fileLocation(&coff.mf, false).offset); + const first_linker_offsets = coff.firstLinkerMemberOffsetsSlice(); + for (member.symbol_offsets.values()) |offset_index| + first_linker_offsets[offset_index] = std.mem.nativeTo(u32, file_offset, .big); + }, } } @@ -2666,12 +3353,14 @@ fn updateExportsInner( ))), }; while (try coff.idle(pt.tid)) {} + const exported_ni = exported_si.node(coff); const exported_sym = exported_si.get(coff); for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); const name = @"export".opts.name.toSlice(ip); - const export_si = try coff.globalSymbol(name, null); + const symbol_gop = try coff.getOrPutGlobalSymbol(name, null); + const export_si = symbol_gop.value_ptr.*; const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; @@ -2687,6 +3376,13 @@ fn updateExportsInner( std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); } + if (coff.isArchive()) + try coff.addMemberSymbol( + symbol_gop.key_ptr.*.name, + coff.getNode(Node.known.zcu_member).archive_member, + export_si, + ); + if (coff.export_table.ni == .none) continue; const entries_ctx = ExportTable.Adapter{ .coff = coff }; @@ -2703,7 +3399,7 @@ fn updateExportsInner( if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); - const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size; + const name_index: u64 = coff.export_table.name_table_ni.location(&coff.mf).resolve(&coff.mf)[1]; const new_name_table_size = name_index + name.len + 1; if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); @@ -2714,19 +3410,23 @@ fn updateExportsInner( @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); // If the new name sorts after the current tail of the sorted list, we don't need to re-sort - const ordinal_table_slice = coff.exportOrdinalTableSlice(); - if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) { - const tail_index: ExportTable.Ordinal = - @enumFromInt(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal); - const tail_entry = tail_index.get(coff); - const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len]; - coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name); + { + const ordinal_table_slice = coff.exportOrdinalTableSlice(); + if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) { + const tail_index: ExportTable.Ordinal = + @enumFromInt(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal); + const tail_entry = tail_index.get(coff); + const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len]; + coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name); + } } const edt = coff.exportDirectoryTable(); coff.targetStore(&edt.number_of_names, @intCast(export_count)); edt.number_of_entries = edt.number_of_names; + // TODO: If we had an estimate of the total number of exports this could be a lot more efficient + try coff.export_table.export_address_table_si.node(coff).resize( &coff.mf, gpa, @@ -2783,22 +3483,24 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe _ = name; } -pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void { +fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void { const comp = coff.base.comp; const io = comp.io; var buffer: [512]u8 = undefined; const stderr = try io.lockStderr(&buffer, null); defer io.unlockStderr(); const w = &stderr.file_writer.interface; - coff.printNode(tid, w, .root, 0) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, - }; + 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 printNode( coff: *Coff, tid: Zcu.PerThread.Id, - w: *std.Io.Writer, + w: *Io.Writer, ni: MappedFile.Node.Index, indent: usize, ) !void { @@ -2830,7 +3532,7 @@ pub fn printNode( const ip = &zcu.intern_pool; const nav = ip.getNav(nmi.navIndex(coff)); try w.print("({f}, {f})", .{ - Type.fromInterned(nav.typeOf(ip)).fmt(.{ .zcu = zcu, .tid = tid }), + Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }), nav.fqn.fmt(ip), }); }, @@ -2876,7 +3578,7 @@ pub fn printNode( const line_len = 0x10; var line_it = std.mem.window( u8, - coff.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], + coff.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], line_len, line_len, ); diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 241ceef582f3ba3a8ae51540b958ad020f9e065d..d0ee6e49bed4fbf5997f73970e0a28d4034395bb 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -6711,16 +6711,8 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm _ = name; } -pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void { - const comp = elf.base.comp; - const io = comp.io; - var buffer: [512]u8 = undefined; - const stderr = try io.lockStderr(&buffer, null); - defer io.lockStderr(); - const w = &stderr.file_writer.interface; - elf.printNode(tid, w, .root, 0) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, - }; +pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !void { + return elf.printNode(tid, w, .root, 0); } pub fn printNode( @@ -6770,7 +6762,7 @@ pub fn printNode( const ip = &zcu.intern_pool; const nav = ip.getNav(nmi.navIndex(elf)); try w.print("({f}, {f})", .{ - Type.fromInterned(nav.typeOf(ip)).fmt(.{ .zcu = zcu, .tid = tid }), + Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }), nav.fqn.fmt(ip), }); }, diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 9c317aff744d99f6570654851bb253670d35f8e8..b05018bb83a4db1d8be9f2e9e5695f0ae1bf7915 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -188,6 +188,10 @@ pub const Node = extern struct { return ni.get(mf).parent; } + pub fn next(ni: Node.Index, mf: *const MappedFile) Node.Index { + return ni.get(mf).next; + } + pub fn ChildIterator(comptime direction: enum { prev, next }) type { return struct { mf: *const MappedFile, @@ -834,6 +838,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested shift = first_floating.flags.alignment.forward(@intCast( @max(shift, first_floating_size), )); + // Not enough space, try the next node last_fixed_ni = first_floating_ni; first_floating_ni = first_floating.next; @@ -1135,6 +1140,8 @@ fn ensureTotalCapacityPreciseInner(mf: *MappedFile, new_capacity: usize) (Alloca error.OperationUnsupported => {}, else => |e| return e, } + + try mf.memory_map.write(io); unmap(mf); } diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 7d9c2e7f53f6e3ba26497650ced048f0e41c1455..32a9083757dca583f834b2dbd1f26e776629d8d0 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -13,7 +13,14 @@ pub fn build(b: *std.Build) void { const lib_use_llvm: []const bool = &.{ true, true, false, false }; for (exe_names, lib_names, lib_link_libc, lib_use_llvm) |exe_name, lib_name, dyn_libc, use_llvm| { - if (target.result.os.tag == .windows and target.result.abi == .gnu and dyn_libc and !use_llvm) + if (!use_llvm and target.result.os.tag == .macos) continue; // TODO: Library not loaded: @rpath/libmathtest-no-llvm.dylib (segment '__CONST_ZIG' vm address out of order) + if (!use_llvm and target.result.os.tag == .freebsd) continue; // TODO: Shared object "libmathtest-no-llvm.so.1" not found + if (!use_llvm and target.result.os.tag == .netbsd) continue; // TODO: Shared object "libmathtest-no-llvm.so.1" not found + if (!use_llvm and target.result.os.tag == .openbsd) continue; // TODO: duplicate symbol definition: atexit + if (!use_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO + if (!use_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO + if (!use_llvm and target.result.cpu.arch == .s390x) continue; // TODO + if (!use_llvm and target.result.os.tag == .windows and target.result.abi == .gnu and dyn_libc) continue; // TODO: sub-compilation of compiler_rt failed (failed to link with LLD: LibCInstallationNotAvailable) const lib = b.addLibrary(.{ @@ -44,6 +51,9 @@ pub fn build(b: *std.Build) void { }); exe.root_module.linkLibrary(lib); + b.getInstallStep().dependOn(&b.addInstallArtifact(lib, .{}).step); + b.getInstallStep().dependOn(&b.addInstallArtifact(exe, .{}).step); + const run_cmd = b.addRunArtifact(exe); test_step.dependOn(&run_cmd.step); } -- 2.54.0 From e7d452338c07fd1c8469ae78ed28451ee43089dc Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 06/94] - MappedFile: add shrinkNode, - MappedFile: avoid resizeNode unintentially growing nodes when a size <= the current size is requested --- src/link/MappedFile.zig | 67 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index b05018bb83a4db1d8be9f2e9e5695f0ae1bf7915..91eeaa2cdf83552e1659173825d1a4eda6a4bf5b 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -375,6 +375,25 @@ pub const Node = extern struct { } } + /// Shrink a node to `size`, exactly. + /// If the new size can't contain all the children, returns error.ShrinkImpossible. + /// If `shift_next` is set, then the following node is shifted backwards into + /// the free space as much as alignment allows. + pub fn shrink( + ni: Node.Index, + mf: *MappedFile, + gpa: std.mem.Allocator, + size: u64, + shift_next: bool, + ) !void { + try mf.shrinkNode(gpa, ni, size, shift_next); + var writers_it = mf.writers.first; + while (writers_it) |writer_node| : (writers_it = writer_node.next) { + const w: *Node.Writer = @fieldParentPtr("writer_node", writer_node); + w.interface.buffer = w.ni.slice(mf); + } + } + pub fn writer(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, w: *Writer) void { w.* = .{ .gpa = gpa, @@ -582,7 +601,9 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { free_node.flags.resized = false; } _, const parent_size = opts.parent.location(mf).resolve(mf); - if (offset > parent_size) try opts.parent.resize(mf, gpa, offset); + const required_parent_size = offset + opts.add_node.size; + if (required_parent_size > parent_size) + try opts.parent.resize(mf, gpa, required_parent_size); try free_ni.resize(mf, gpa, opts.add_node.size); } if (opts.add_node.moved) free_ni.movedAssumeCapacity(mf); @@ -670,11 +691,55 @@ pub fn addNodeAfter( }); } +fn shrinkNode( + mf: *MappedFile, + gpa: std.mem.Allocator, + ni: Node.Index, + size: u64, + shrink_next: bool, +) !void { + const node = ni.get(mf); + const old_offset, _ = node.location().resolve(mf); + + // This would require unmapping first + if (ni == Node.Index.root) return error.Unimplemented; + + if (node.last != .none) { + const last = node.last.get(mf); + const last_offset, const last_size = last.location().resolve(mf); + if (last_offset + last_size > size) return error.ShrinkImpossible; + } + + try mf.large.ensureUnusedCapacity(gpa, 4); + try mf.updates.ensureUnusedCapacity(gpa, 2); + + ni.setLocationAssumeCapacity(mf, old_offset, size); + if (!shrink_next or node.next == .none) return; + + const next = node.next.get(mf); + const old_next_offset, const next_size = next.location().resolve(mf); + const padding = old_next_offset - (old_offset + size); + const new_next_offset = next.flags.alignment.forward(@intCast(old_next_offset - padding)); + + if (next.flags.has_content and new_next_offset < old_next_offset) { + const old_file_offset = node.next.fileLocation(mf, false).offset; + const new_file_offset = (old_file_offset - old_next_offset) + new_next_offset; + @memmove( + mf.memory_map.memory[new_file_offset..][0..next_size], + mf.memory_map.memory[old_file_offset..][0..next_size], + ); + } + + node.next.setLocationAssumeCapacity(mf, new_next_offset, next_size); +} + fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) (Allocator.Error || Io.Cancelable || IoError)!void { const io = mf.io; 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) { try mf.ensureCapacityForSetLocation(gpa); -- 2.54.0 From f81bd30057d6a1d1d50851b65a644aa86296cd4c Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 07/94] Coff: Symbol table output --- lib/std/coff.zig | 18 +- src/libs/mingw/implib.zig | 2 +- src/link/Coff.zig | 429 ++++++++++++++++++++++++++++++++++---- 3 files changed, 393 insertions(+), 56 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 742f9d0a9af8537f571a236fa9aec5ea31bb536d..904e5be36ce74776b5cdc833c93936ae4475655a 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -658,7 +658,7 @@ pub const SectionHeader = extern struct { }; }; -pub const Symbol = struct { +pub const Symbol = extern struct { name: [8]u8, value: u32, section_number: SectionNumber, @@ -683,18 +683,18 @@ pub const Symbol = struct { } }; -pub const SectionNumber = enum(u16) { +pub const SectionNumber = enum(i16) { /// The symbol record is not yet assigned a section. /// A value of zero indicates that a reference to an external symbol is defined elsewhere. /// A value of non-zero is a common symbol with a size that is specified by the value. UNDEFINED = 0, /// The symbol has an absolute (non-relocatable) value and is not an address. - ABSOLUTE = 0xffff, + ABSOLUTE = -1, /// The symbol provides general type or debugging information but does not correspond to a section. /// Microsoft tools use this setting along with .file records (storage class FILE). - DEBUG = 0xfffe, + DEBUG = -2, _, }; @@ -866,7 +866,7 @@ pub const StorageClass = enum(u8) { _, }; -pub const FunctionDefinition = struct { +pub const FunctionDefinition = extern struct { /// The symbol-table index of the corresponding .bf (begin function) symbol record. tag_index: u32, @@ -885,7 +885,7 @@ pub const FunctionDefinition = struct { unused: [2]u8, }; -pub const SectionDefinition = struct { +pub const SectionDefinition = extern struct { /// The size of section data; the same as SizeOfRawData in the section header. length: u32, @@ -907,7 +907,7 @@ pub const SectionDefinition = struct { unused: [3]u8, }; -pub const FileDefinition = struct { +pub const FileDefinition = extern struct { /// An ANSI string that gives the name of the source file. /// This is padded with nulls if it is less than the maximum length. file_name: [18]u8, @@ -918,7 +918,7 @@ pub const FileDefinition = struct { } }; -pub const WeakExternalDefinition = struct { +pub const WeakExternalDefinition = extern struct { /// The symbol-table index of sym2, the symbol to be linked if sym1 is not found. tag_index: u32, @@ -977,7 +977,7 @@ pub const ComdatSelection = enum(u8) { _, }; -pub const DebugInfoDefinition = struct { +pub const DebugInfoDefinition = extern struct { unused_1: [4]u8, /// The actual ordinal line number (1, 2, 3, and so on) within the source file, corresponding to the .bf or .ef record. diff --git a/src/libs/mingw/implib.zig b/src/libs/mingw/implib.zig index 0c4ca824863cd411435e2bb44aee95d9a4a680f9..f3cf24b3b88cf41323fbe62d4890277ca8ca5f8b 100644 --- a/src/libs/mingw/implib.zig +++ b/src/libs/mingw/implib.zig @@ -1012,7 +1012,7 @@ fn getShortImport( fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void { try writer.writeAll(&symbol.name); try writer.writeInt(u32, symbol.value, .little); - try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little); + try writer.writeInt(i16, @intFromEnum(symbol.section_number), .little); try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little); try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little); try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index d019efc3d8640b4a88e013157af8946b8d5f762b..a676637e5c5afd52b63d095ce269cc5dd46bee51 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -30,6 +30,7 @@ lib_string_len: u64, long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, +symbol_table: SymbolTable, strings: std.HashMapUnmanaged( u32, void, @@ -37,10 +38,10 @@ strings: std.HashMapUnmanaged( std.hash_map.default_max_load_percentage, ), string_bytes: std.ArrayList(u8), -image_section_table: std.ArrayList(Symbol.Index), +section_table: std.ArrayList(Symbol.Index), pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), -symbol_table: std.ArrayList(Symbol), +symbols: std.ArrayList(Symbol), globals: std.array_hash_map.Auto(GlobalName, Symbol.Index), global_pending_index: u32, navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index), @@ -144,6 +145,7 @@ pub const Node = union(enum) { header, /// Images and archives only. signature, + /// Archives only. archive_member_header: Member.Index, archive_member: Member.Index, @@ -154,15 +156,21 @@ pub const Node = union(enum) { /// Image only data_directories, section_table, - image_section: Symbol.Index, + // Archives and objects only + symbol_table, + symbol_table_entry, + // Archives and objects only + string_table, - /// Only images contain imports + image_section: Symbol.Index, // TODO: image_section -> section + + /// Images only import_directory_table, import_lookup_table: ImportTable.Index, import_address_table: ImportTable.Index, import_hint_name_table: ImportTable.Index, - /// Only images contain exports + /// Images only export_directory_table, export_address_table, export_name_pointer_table, @@ -291,6 +299,8 @@ pub const Node = union(enum) { optional_header, data_directories, section_table, + symbol_table, + string_table, }; var mut_known: std.enums.EnumFieldStruct(Known, MappedFile.Node.Index, null) = undefined; const info = @typeInfo(Known).@"enum"; @@ -436,6 +446,47 @@ pub const LongNamesTable = struct { }; }; +pub const SymbolTable = struct { + string_offsets: std.AutoArrayHashMapUnmanaged(String, StringIndex), + entries: std.AutoArrayHashMapUnmanaged(Symbol.Index, Entry), + + // Adding nodes to the symbol table has the result of accumulating padding + // between the last symbol and the string table, due to the growth factor + // in MappedFile. The spec requires the string table begin immediately + // after the last symbol, so we compact the symbol table node if needed. + pending_shrink: bool, + + pub const Entry = struct { + entry_si: Symbol.Index, + index: Index, + }; + + pub const Add = union(enum) { + section, + global: struct { + import: bool, + }, + }; + + pub const SymbolName = union(enum) { + short: []const u8, + long: StringIndex, + }; + + // Symbol.Index does not map 1:1 with SymbolTable.Index due to auxiliary entries + pub const Index = enum(u32) { + _, + + pub fn get(sti: SymbolTable.Index, coff: *Coff) *Entry { + return &coff.symbol_table.entries.values()[@intFromEnum(sti)]; + } + }; + + pub const StringIndex = enum(u32) { + _, + }; +}; + pub const ExportTable = struct { ni: MappedFile.Node.Index, export_directory_table_ni: MappedFile.Node.Index, @@ -582,7 +633,7 @@ pub const Symbol = struct { } pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index { - return coff.image_section_table.items[sn.toIndex()]; + return coff.section_table.items[sn.toIndex()]; } pub fn header(sn: SectionNumber, coff: *Coff) *std.coff.SectionHeader { @@ -600,7 +651,7 @@ pub const Symbol = struct { const known_count = @typeInfo(Index).@"enum".field_names.len; pub fn get(si: Symbol.Index, coff: *Coff) *Symbol { - return &coff.symbol_table.items[@intFromEnum(si)]; + return &coff.symbols.items[@intFromEnum(si)]; } pub fn node(si: Symbol.Index, coff: *Coff) MappedFile.Node.Index { @@ -920,12 +971,17 @@ fn create( .name_table_ni = .none, .entries = .empty, }, + .symbol_table = .{ + .string_offsets = .empty, + .entries = .empty, + .pending_shrink = false, + }, .strings = .empty, .string_bytes = .empty, - .image_section_table = .empty, + .section_table = .empty, .pseudo_section_table = .empty, .object_section_table = .empty, - .symbol_table = .empty, + .symbols = .empty, .globals = .empty, .global_pending_index = 0, .navs = .empty, @@ -965,15 +1021,16 @@ pub fn deinit(coff: *Coff) void { const gpa = coff.base.comp.gpa; coff.mf.deinit(gpa); coff.nodes.deinit(gpa); + // TODO: Update this coff.long_names_table.entries.deinit(gpa); coff.import_table.entries.deinit(gpa); coff.export_table.entries.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); - coff.image_section_table.deinit(gpa); + coff.section_table.deinit(gpa); coff.pseudo_section_table.deinit(gpa); coff.object_section_table.deinit(gpa); - coff.symbol_table.deinit(gpa); + coff.symbols.deinit(gpa); coff.globals.deinit(gpa); coff.navs.deinit(gpa); coff.uavs.deinit(gpa); @@ -1035,8 +1092,13 @@ fn initHeaders( var expected_nodes_len: usize = Node.known_count; if (comp.zcu != null) { + // Section nodes expected_nodes_len += 3; + // Symbol table nodes + if (is_archive) expected_nodes_len += 6; + // Pseudo-sections and import / export table nodes if (is_image) expected_nodes_len += 9; + // TLS section nodes expected_nodes_len += @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2; } defer assert(coff.nodes.len == expected_nodes_len); @@ -1294,10 +1356,26 @@ fn initHeaders( })); coff.nodes.appendAssumeCapacity(.section_table); + const symbol_table_ni = Node.known.symbol_table; + assert(symbol_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + .alignment = .@"4", + .fixed = true, + .moved = true, + })); + coff.nodes.appendAssumeCapacity(.symbol_table); + + const string_table_ni = Node.known.string_table; + assert(string_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + .size = @sizeOf(u32), + .fixed = true, + .resized = true, + })); + coff.nodes.appendAssumeCapacity(.string_table); + assert(coff.nodes.len == Node.known_count); - try coff.symbol_table.ensureTotalCapacity(gpa, Symbol.Index.known_count); - coff.symbol_table.addOneAssumeCapacity().* = .{ + try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); + coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, .rva = 0, .size = 0, @@ -1360,7 +1438,7 @@ fn initHeaders( }); coff.nodes.appendAssumeCapacity(.export_address_table); - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity(); const export_address_table_sym = coff.export_table.export_address_table_si.get(coff); @@ -1451,6 +1529,10 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .section_table, .export_name_table, .placeholder, + + .symbol_table, + .symbol_table_entry, + .string_table, => unreachable, .image_section => |si| si, .import_directory_table => break :parent_rva coff.targetLoad( @@ -1632,7 +1714,28 @@ pub fn dataDirectoryPtr( } pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader { - return @ptrCast(@alignCast(Node.known.section_table.slice(&coff.mf))); + return @ptrCast(@alignCast( + Node.known.section_table.slice(&coff.mf)[0 .. coff.section_table.items.len * @sizeOf(std.coff.SectionHeader)], + )); +} + +pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) *align(2) std.coff.Symbol { + return @ptrCast(@alignCast( + &Node.known.symbol_table.slice(&coff.mf)[@intFromEnum(sti) * std.coff.Symbol.sizeOf()], + )); +} + +pub fn symbolAuxSectionDefinitionPtr(coff: *Coff, si: Symbol.Index) *align(2) std.coff.SectionDefinition { + const sti = coff.symbol_table.entries.get(si).?.index; + + const symbol = coff.symbolTableEntryPtr(sti); + assert(symbol.storage_class == .STATIC and symbol.number_of_aux_symbols == 1); + + return @ptrCast(symbolTableEntryPtr(coff, @enumFromInt(@intFromEnum(sti) + 1))); +} + +pub fn symbolTableStringLenPtr(coff: *Coff) *align(2) u32 { + return @ptrCast(@alignCast(Node.known.string_table.slice(&coff.mf)[0..@sizeOf(u32)])); } pub fn importDirectoryTableSlice(coff: *Coff) []std.coff.ImportDirectoryEntry { @@ -1662,7 +1765,7 @@ pub fn exportOrdinalTableSlice(coff: *Coff) []std.coff.ExportOrdinalTableEntry { } fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { - defer coff.symbol_table.addOneAssumeCapacity().* = .{ + defer coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, .rva = 0, .size = 0, @@ -1670,7 +1773,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .target_relocs = .none, .section_number = .UNDEFINED, }; - return @enumFromInt(coff.symbol_table.items.len); + return @enumFromInt(coff.symbols.items.len); } fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index { @@ -1715,7 +1818,7 @@ fn getOrPutGlobalSymbol( lib_name: ?[]const u8, ) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult { const gpa = coff.base.comp.gpa; - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.globals.getOrPut(gpa, .{ .name = try coff.getOrPutString(name), .lib_name = try coff.getOrPutOptionalString(lib_name), @@ -1724,6 +1827,7 @@ fn getOrPutGlobalSymbol( sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); coff.synth_prog_node.increaseEstimatedTotalItems(1); } + return sym_gop; } @@ -1758,7 +1862,7 @@ fn navSection( } fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavMapIndex { const gpa = zcu.gpa; - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.navs.getOrPut(gpa, nav_index); if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); return @enumFromInt(sym_gop.index); @@ -1776,7 +1880,7 @@ pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbo fn uavMapIndex(coff: *Coff, uav_val: InternPool.Index) !Node.UavMapIndex { const gpa = coff.base.comp.gpa; - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.uavs.getOrPut(gpa, uav_val); if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); return @enumFromInt(sym_gop.index); @@ -1788,7 +1892,7 @@ pub fn uavSymbol(coff: *Coff, uav_val: InternPool.Index) !Symbol.Index { pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index { const gpa = coff.base.comp.gpa; - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.lazy.getPtr(lazy.kind).map.getOrPut(gpa, lazy.ty); if (!sym_gop.found_existing) { sym_gop.value_ptr.* = try coff.initSymbolAssumeCapacity(); @@ -2004,13 +2108,185 @@ fn addMemberSymbol( coff.pending_members.putAssumeCapacity(mi, {}); } +fn addSymbolTableEntry( + coff: *Coff, + name: union(enum) { + bytes: []const u8, + string: String, + }, + si: Symbol.Index, + add: SymbolTable.Add, +) !void { + assert(!coff.isImage()); + const gpa = coff.base.comp.gpa; + + const string, const name_slice = switch (name) { + .bytes => |bytes| .{ try coff.getOrPutString(bytes), bytes }, + .string => |s| .{ s, s.toSlice(coff) }, + }; + + const symbol_name: SymbolTable.SymbolName = if (name_slice.len > 8) index: { + const string_gop = try coff.symbol_table.string_offsets.getOrPut(gpa, string); + if (!string_gop.found_existing) { + const string_index = Node.known.string_table.location(&coff.mf).resolve(&coff.mf)[1]; + string_gop.value_ptr.* = @enumFromInt(string_index); + + try Node.known.string_table.resize(&coff.mf, gpa, string_index + name_slice.len + 1); + const slice = Node.known.string_table.slice(&coff.mf); + @memcpy(slice[string_index..][0..name_slice.len], name_slice); + slice[string_index + name_slice.len] = 0; + } + + break :index .{ .long = string_gop.value_ptr.* }; + } else .{ .short = name_slice }; + + const symbol_index = coff.targetLoad(&coff.headerPtr().number_of_symbols); + const symbols_added: u8 = switch (add) { + .section => count: { + const sym = si.get(coff); + + try coff.nodes.ensureUnusedCapacity(gpa, 2); + _ = try coff.addSymbolTableEntryAssumeCapacity( + symbol_name, + 0, + sym.section_number, + .{ + .complex_type = .NULL, + .base_type = .NULL, + }, + .STATIC, + 1, + ); + + // Aux entry ields are updated by flushMoved / flushResized + + try coff.symbol_table.entries.put(gpa, si, .{ + .entry_si = .null, + .index = @enumFromInt(symbol_index), + }); + + break :count 2; + }, + .global => |global| count: { + const sym = si.get(coff); + + try coff.nodes.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); + + const entry_ni = try coff.addSymbolTableEntryAssumeCapacity( + symbol_name, + if (global.import) 0 else coff.computeNodeSectionOffset(sym.ni), + if (global.import) .UNDEFINED else sym.section_number, + .{ + .base_type = .NULL, + .complex_type = if (global.import or + Symbol.Index.text.get(coff).section_number == sym.section_number) + .FUNCTION + else + .NULL, + }, + .EXTERNAL, + 0, + ); + + const entry_si = coff.addSymbolAssumeCapacity(); + { + const entry_sym = entry_si.get(coff); + entry_sym.ni = entry_ni; + assert(entry_sym.loc_relocs == .none); + entry_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + entry_sym.section_number = .UNDEFINED; + } + + try coff.addReloc( + entry_si, + @offsetOf(std.coff.Symbol, "value"), + si, + 0, + .{ .AMD64 = .SECREL }, + ); + + try coff.symbol_table.entries.put(gpa, si, .{ + .entry_si = entry_si, + .index = @enumFromInt(symbol_index), + }); + + break :count 1; + }, + }; + + const new_num_symbols = symbol_index + symbols_added; + coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); + coff.symbol_table.pending_shrink = + Node.known.symbol_table.location(&coff.mf).resolve(&coff.mf)[1] > + new_num_symbols * std.coff.Symbol.sizeOf(); +} + +/// Caller guarantees there is capacity for 1 + number_of_aux_symbols nodes. +/// Auxiliary nodes are zero-initialized. +fn addSymbolTableEntryAssumeCapacity( + coff: *Coff, + name: SymbolTable.SymbolName, + value: u32, + section_number: Symbol.SectionNumber, + @"type": std.coff.SymType, + storage_class: std.coff.StorageClass, + number_of_aux_symbols: u8, +) !MappedFile.Node.Index { + const gpa = coff.base.comp.gpa; + + const entry_ni = try coff.mf.addLastChildNode(gpa, Node.known.symbol_table, .{ + .alignment = .@"2", + .size = std.coff.Symbol.sizeOf(), + .fixed = true, + }); + coff.nodes.appendAssumeCapacity(.symbol_table_entry); + + const entry: *align(2) std.coff.Symbol = @ptrCast(@alignCast(entry_ni.slice(&coff.mf))); + entry.* = .{ + .name = undefined, + .value = value, + .section_number = @enumFromInt(@intFromEnum(section_number)), + .type = @"type", + .storage_class = storage_class, + .number_of_aux_symbols = number_of_aux_symbols, + }; + + switch (name) { + .short => |s| { + @memcpy(entry.name[0..s.len], s); + @memset(entry.name[s.len..], 0); + }, + .long => |l| { + @memset(entry.name[0..4], 0); + const offset_ptr: *align(2) u32 = @ptrCast(entry.name[4..]); + coff.targetStore(offset_ptr, @intFromEnum(l)); + }, + } + + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.SectionHeader, entry.*); + + for (0..number_of_aux_symbols) |_| { + const aux_ni = try coff.mf.addLastChildNode(gpa, Node.known.symbol_table, .{ + .alignment = .@"2", + .size = std.coff.Symbol.sizeOf(), + .fixed = true, + }); + coff.nodes.appendAssumeCapacity(.symbol_table_entry); + @memset(aux_ni.slice(&coff.mf), 0); + } + + return entry_ni; +} + fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index { assert(coff.base.comp.zcu != null); const gpa = coff.base.comp.gpa; try coff.nodes.ensureUnusedCapacity(gpa, 1); - try coff.image_section_table.ensureUnusedCapacity(gpa, 1); - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.section_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const coff_header = coff.headerPtr(); const section_index = coff.targetLoad(&coff_header.number_of_sections); @@ -2022,19 +2298,19 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags @sizeOf(std.coff.SectionHeader) * section_table_len, ); - const parent_ni, const alignment = if (coff.isArchive()) - .{ Node.known.zcu_member, .@"1" } + const parent_ni = if (coff.isArchive()) + Node.known.zcu_member else - .{ Node.known.file, coff.mf.flags.block_size }; + Node.known.file; const ni = try coff.mf.addLastChildNode(gpa, parent_ni, .{ - .alignment = alignment, + .alignment = coff.mf.flags.block_size, .moved = true, .bubbles_moved = false, }); const si = coff.addSymbolAssumeCapacity(); - coff.image_section_table.appendAssumeCapacity(si); + coff.section_table.appendAssumeCapacity(si); coff.nodes.appendAssumeCapacity(.{ .image_section = si }); const section_table = coff.sectionTableSlice(); @@ -2042,7 +2318,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags const virtual_size = coff.optionalHeaderField(.section_alignment); const rva: u32 = switch (section_index) { 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]), - else => coff.image_section_table.items[section_index - 1].get(coff).rva + + else => coff.section_table.items[section_index - 1].get(coff).rva + coff.targetLoad(§ion_table[section_index - 1].virtual_size), }; @@ -2080,6 +2356,8 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags @intCast(rva + virtual_size), ), } + } else { + try coff.addSymbolTableEntry(.{ .bytes = name }, si, .section); } return si; @@ -2112,7 +2390,7 @@ fn pseudoSectionMapIndex( else .rdata; try coff.nodes.ensureUnusedCapacity(gpa, 1); - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const ni = try coff.mf.addLastChildNode(gpa, parent.node(coff), .{ .alignment = alignment }); const si = coff.addSymbolAssumeCapacity(); pseudo_section_gop.value_ptr.* = si; @@ -2142,7 +2420,7 @@ fn objectSectionMapIndex( name_slice[0 .. std.mem.indexOfScalar(u8, name_slice, '$') orelse name_slice.len], ), alignment, attributes)).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const parent_ni = parent.node(coff); var prev_ni: MappedFile.Node.Index = .none; var next_it = parent_ni.children(&coff.mf); @@ -2251,6 +2529,8 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde const sym = si.get(coff); sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; + + // TODO: Add symbol table entry }, else => si.deleteLocationRelocs(coff), } @@ -2506,11 +2786,6 @@ pub fn flush( _ = prog_node; while (try coff.idle(tid)) {} - // TODO: Second linker member symbol tables are built here - if (isArchive(coff)) { - //Member.Index.second.get(coff).content_ni; - } - const comp = coff.base.comp; // Implib generation should instead be done via building a MappedFile progressively @@ -2634,6 +2909,26 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { coff.flushExportsSort(); break :task; } + // TODO: This and the above task ideally run only once, as it's wasteful otherwise + if (coff.symbol_table.pending_shrink) { + coff.symbol_table.pending_shrink = false; + // TODO: Prog node + const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); + Node.known.symbol_table.shrink( + &coff.mf, + comp.gpa, + number_of_symbols * std.coff.Symbol.sizeOf(), + true, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return comp.link_diags.fail( + "linker failed to shrink symbol table: {t}", + .{e}, + ), + }; + + break :task; + } } if (coff.pending_uavs.count() > 0) return true; if (coff.globals.count() > coff.global_pending_index) return true; @@ -2641,6 +2936,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.mf.updates.items.len > 0) return true; if (coff.pending_members.count() > 0) return true; if (coff.export_table.pending_sort) return true; + if (coff.symbol_table.pending_shrink) return true; return false; } @@ -2733,14 +3029,28 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const gpa = zcu.gpa; const gn = gmi.globalName(coff); - // TODO: We still need to emit a reloc for the __imp_Name symbol? + if (!coff.isImage()) { + // TODO: What about data imports? - if (!coff.isImage()) return; + // const si = gmi.symbol(coff); + // const sym = si.get(coff); + // sym.section_number = Symbol.Index.text.get(coff).section_number; + // assert(sym.loc_relocs == .none); + // sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + // + // try coff.addSymbolTableEntry( + // .{ .bytes = gn.name.toSlice(coff) }, + // si, + // .{ .global = .{ .import = true } }, + // ); + + return; + } if (gn.lib_name.toSlice(coff)) |lib_name| { const name = gn.name.toSlice(coff); try coff.nodes.ensureUnusedCapacity(gpa, 4); - try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 1); const target_endian = coff.targetEndian(); const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); @@ -2749,7 +3059,6 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { .PE32 => .{ 4, .@"4" }, .@"PE32+" => .{ 8, .@"8" }, }; - const gop = try coff.import_table.entries.getOrPutAdapted( gpa, lib_name, @@ -2959,7 +3268,16 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .data_directories, .section_table, .placeholder, + + .symbol_table_entry, + .string_table, => if (!coff.isArchive()) unreachable, + .symbol_table => { + coff.targetStore( + &coff.headerPtr().pointer_to_symbol_table, + @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]), + ); + }, .archive_member_header => |mi| { const member = mi.get(coff); switch (member.kind) { @@ -3121,7 +3439,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ), } - if (size > coff.image_section_table.items[0].get(coff).rva) try coff.virtualSlide( + if (size > coff.section_table.items[0].get(coff).rva) try coff.virtualSlide( 0, std.mem.alignForward( u32, @@ -3159,6 +3477,12 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { .data_directories, => unreachable, .section_table => {}, + .symbol_table => assert(!coff.isImage()), + .symbol_table_entry => unreachable, + .string_table => { + assert(!coff.isImage()); + coff.targetStore(coff.symbolTableStringLenPtr(), @intCast(size)); + }, .image_section => |si| { const sym = si.get(coff); const section_index = sym.section_number.toIndex(); @@ -3173,6 +3497,13 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.targetStore(§ion.virtual_size, virtual_size); try coff.virtualSlide(section_index + 1, sym.rva + virtual_size); } + + if (coff.isArchive()) { + coff.targetStore( + &coff.symbolAuxSectionDefinitionPtr(si).length, + @intCast(size), + ); + } }, .import_directory_table => coff.targetStore( &coff.dataDirectoryPtr(.IMPORT).size, @@ -3298,7 +3629,7 @@ fn flushExportsSort(coff: *Coff) void { fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void { var rva = start_rva; for ( - coff.image_section_table.items[start_section_index..], + coff.section_table.items[start_section_index..], coff.sectionTableSlice()[start_section_index..], ) |section_si, *section| { const section_sym = section_si.get(coff); @@ -3343,7 +3674,7 @@ fn updateExportsInner( Value.fromInterned(uav).fmtValue(pt), }), } - try coff.symbol_table.ensureUnusedCapacity(gpa, export_indices.len); + try coff.symbols.ensureUnusedCapacity(gpa, export_indices.len); const exported_si: Symbol.Index = switch (exported) { .nav => |nav| try coff.navSymbol(zcu, nav), .uav => |uav| @enumFromInt(@intFromEnum(try coff.lowerUav( @@ -3376,13 +3707,20 @@ fn updateExportsInner( std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); } - if (coff.isArchive()) + if (coff.isArchive()) { try coff.addMemberSymbol( symbol_gop.key_ptr.*.name, coff.getNode(Node.known.zcu_member).archive_member, export_si, ); + try coff.addSymbolTableEntry( + .{ .bytes = name }, + export_si, + .{ .global = .{ .import = false } }, + ); + } + if (coff.export_table.ni == .none) continue; const entries_ctx = ExportTable.Adapter{ .coff = coff }; @@ -3425,8 +3763,7 @@ fn updateExportsInner( coff.targetStore(&edt.number_of_names, @intCast(export_count)); edt.number_of_entries = edt.number_of_names; - // TODO: If we had an estimate of the total number of exports this could be a lot more efficient - + // TODO: These should all be resized ahead of time to fit all exports (after https://github.com/ziglang/zig/issues/23616) try coff.export_table.export_address_table_si.node(coff).resize( &coff.mf, gpa, -- 2.54.0 From 8c2737fd95f46d43a13a69a0a2cdedecb61a1157 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 08/94] Coff: Start working on symbol and relocation tables --- src/codegen/x86_64/Emit.zig | 12 +- src/link/Coff.zig | 466 ++++++++++++++++++++++++++---------- 2 files changed, 339 insertions(+), 139 deletions(-) diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index babadbe96b8bf23db4ad0d4c10de06c11a7710ca..2dd95342d3379307157fa9738ae0bcd82e8e2b9c 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -161,13 +161,13 @@ pub fn emitMir(emit: *Emit) Error!void { .type = .FUNC, }) else if (emit.bin_file.cast(.macho)) |macho_file| @enumFromInt(try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)) - else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol( - extern_func.toSlice(&emit.lower.mir).?, - switch (comp.compiler_rt_strat) { + else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol(.{ + .name = extern_func.toSlice(&emit.lower.mir).?, + .lib_name = switch (comp.compiler_rt_strat) { .none, .lib, .obj, .zcu => null, .dyn_lib => "compiler_rt", }, - ))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}), + }))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}), .is_extern = true, } }, }, @@ -374,7 +374,7 @@ pub fn emitMir(emit: *Emit) Error!void { .op_index = 1, .target = .{ .symbol = .{ .symbol = @enumFromInt(@intFromEnum( - try coff.globalSymbol("__tls_index", null), + try coff.globalSymbol(.{ .name = "__tls_index" }), )), .is_extern = false, } }, @@ -409,7 +409,7 @@ pub fn emitMir(emit: *Emit) Error!void { .op_index = 1, .target = .{ .symbol = .{ .symbol = @enumFromInt(@intFromEnum( - try coff.globalSymbol("_tls_index", null), + try coff.globalSymbol(.{ .name = "_tls_index" }), )), .is_extern = false, } }, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a676637e5c5afd52b63d095ce269cc5dd46bee51..ee67523d09496db6fb570a8be730ec3fb74aee7d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,7 +38,7 @@ strings: std.HashMapUnmanaged( std.hash_map.default_max_load_percentage, ), string_bytes: std.ArrayList(u8), -section_table: std.ArrayList(Symbol.Index), +section_table: std.ArrayList(Section), pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), symbols: std.ArrayList(Symbol), @@ -161,8 +161,11 @@ pub const Node = union(enum) { symbol_table_entry, // Archives and objects only string_table, + // Archives and objects only + relocation_table: Symbol.SectionNumber, + relocation_table_entry: Reloc.Index, - image_section: Symbol.Index, // TODO: image_section -> section + image_section: Symbol.Index, // TODO: rename image_section -> section /// Images only import_directory_table, @@ -318,9 +321,7 @@ pub const Member = struct { kind: Kind, header_ni: MappedFile.Node.Index, content_ni: MappedFile.Node.Index, - // Maps symbols contained in this member to their index in the first linker member's symbol table - // TODO: This could contain information about the name string if we need - symbol_offsets: std.AutoArrayHashMapUnmanaged(Symbol.Index, u33), + first_linker_indices: std.AutoArrayHashMapUnmanaged(Symbol.Index, FirstLinkerIndex), pub const Kind = enum { first_linker, @@ -343,6 +344,10 @@ pub const Member = struct { } }; + pub const FirstLinkerIndex = enum(u32) { + _, + }; + pub fn headerPtr(member: *Member, coff: *Coff) *std.coff.ArchiveMemberHeader { return @ptrCast(@alignCast(member.header_ni.slice(&coff.mf))); } @@ -458,14 +463,12 @@ pub const SymbolTable = struct { pub const Entry = struct { entry_si: Symbol.Index, - index: Index, + sti: Index, // TODO: Is this redundant now that we store it on symbol? }; pub const Add = union(enum) { section, - global: struct { - import: bool, - }, + global, }; pub const SymbolName = union(enum) { @@ -473,12 +476,25 @@ pub const SymbolTable = struct { long: StringIndex, }; - // Symbol.Index does not map 1:1 with SymbolTable.Index due to auxiliary entries + // Symbol.Index does not map 1:1 with SymbolTable.Index due to + // variable number of auxiliary entries that may trail each symbol pub const Index = enum(u32) { + none, _, + pub fn wrap(i: ?u32) Index { + return @enumFromInt((i orelse return .none) + 1); + } + + pub fn unwrap(sti: Index) ?u32 { + return switch (sti) { + .none => null, + _ => @intFromEnum(sti) - 1, + }; + } + pub fn get(sti: SymbolTable.Index, coff: *Coff) *Entry { - return &coff.symbol_table.entries.values()[@intFromEnum(sti)]; + return &coff.symbol_table.entries.values()[sti.unwrap().?]; } }; @@ -607,6 +623,37 @@ pub const String = enum(u32) { } }; +pub const Section = struct { + si: Symbol.Index, + relocation_table_ni: MappedFile.Node.Index, + + pub const RelocationIndex = enum(u32) { + none, + _, + + pub fn wrap(i: ?u32) RelocationIndex { + return @enumFromInt((i orelse return .none) + 1); + } + + pub fn unwrap(sri: RelocationIndex) ?u32 { + return switch (sri) { + .none => null, + _ => @intFromEnum(sri) - 1, + }; + } + + pub fn entry( + sri: RelocationIndex, + coff: *Coff, + sn: Symbol.SectionNumber, + ) ?*align(2) std.coff.Relocation { + if (sri == .none) return null; + const table_slice = sn.section(coff).relocation_table_ni.slice(&coff.mf); + return @ptrCast(@alignCast(&table_slice[sri.unwrap().? * std.coff.Relocation.sizeOf()])); + } + }; +}; + pub const GlobalName = struct { name: String, lib_name: String.Optional }; pub const Symbol = struct { @@ -618,9 +665,9 @@ pub const Symbol = struct { /// Relocations targeting this symbol target_relocs: Reloc.Index, section_number: SectionNumber, + sti: SymbolTable.Index, unused0: u32 = 0, - unused1: u32 = 0, - unused2: u16 = 0, + unused1: u16 = 0, pub const SectionNumber = enum(i16) { UNDEFINED = 0, @@ -633,7 +680,11 @@ pub const Symbol = struct { } pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index { - return coff.section_table.items[sn.toIndex()]; + return sn.section(coff).si; + } + + pub fn section(sn: SectionNumber, coff: *const Coff) *Section { + return &coff.section_table.items[sn.toIndex()]; } pub fn header(sn: SectionNumber, coff: *Coff) *std.coff.SectionHeader { @@ -692,6 +743,17 @@ pub const Symbol = struct { } sym.loc_relocs = .none; } + + pub fn updateRelocsSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { + const sym = si.get(coff); + var ri = sym.target_relocs; + while (ri != .none) { + const reloc = ri.get(coff); + if (reloc.sri.entry(coff, reloc.loc.get(coff).section_number)) |entry| + coff.targetStore(&entry.symbol_table_index, sym.sti.unwrap().?); + ri = reloc.next; + } + } }; comptime { @@ -705,7 +767,7 @@ pub const Reloc = extern struct { next: Reloc.Index, loc: Symbol.Index, target: Symbol.Index, - unused: u32, + sri: Section.RelocationIndex, offset: u64, addend: i64, @@ -725,8 +787,8 @@ pub const Reloc = extern struct { none = std.math.maxInt(u32), _, - pub fn get(si: Reloc.Index, coff: *Coff) *Reloc { - return &coff.relocs.items[@intFromEnum(si)]; + pub fn get(ri: Reloc.Index, coff: *Coff) *Reloc { + return &coff.relocs.items[@intFromEnum(ri)]; } }; @@ -861,6 +923,8 @@ pub const Reloc = extern struct { } pub fn delete(reloc: *Reloc, coff: *Coff) void { + // TODO: Need to remove this from the COFF relocation table (remove swap) + switch (reloc.prev) { .none => { const target = reloc.target.get(coff); @@ -1021,10 +1085,11 @@ pub fn deinit(coff: *Coff) void { const gpa = coff.base.comp.gpa; coff.mf.deinit(gpa); coff.nodes.deinit(gpa); - // TODO: Update this coff.long_names_table.entries.deinit(gpa); coff.import_table.entries.deinit(gpa); coff.export_table.entries.deinit(gpa); + coff.symbol_table.string_offsets.deinit(gpa); + coff.symbol_table.entries.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); coff.section_table.deinit(gpa); @@ -1356,9 +1421,10 @@ fn initHeaders( })); coff.nodes.appendAssumeCapacity(.section_table); + // TODO: These two nodes could be inside one movable node const symbol_table_ni = Node.known.symbol_table; assert(symbol_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ - .alignment = .@"4", + .alignment = .@"2", .fixed = true, .moved = true, })); @@ -1366,7 +1432,8 @@ fn initHeaders( const string_table_ni = Node.known.string_table; assert(string_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ - .size = @sizeOf(u32), + .alignment = .@"2", + .size = if (!is_image) @sizeOf(u32) else 0, .fixed = true, .resized = true, })); @@ -1382,6 +1449,7 @@ fn initHeaders( .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, + .sti = .none, }; assert(try coff.addSection(".data", .{ .CNT_INITIALIZED_DATA = true, @@ -1533,6 +1601,8 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .symbol_table, .symbol_table_entry, .string_table, + .relocation_table, + .relocation_table_entry, => unreachable, .image_section => |si| si, .import_directory_table => break :parent_rva coff.targetLoad( @@ -1721,17 +1791,18 @@ pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader { pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) *align(2) std.coff.Symbol { return @ptrCast(@alignCast( - &Node.known.symbol_table.slice(&coff.mf)[@intFromEnum(sti) * std.coff.Symbol.sizeOf()], + &Node.known.symbol_table.slice(&coff.mf)[sti.unwrap().? * std.coff.Symbol.sizeOf()], )); } pub fn symbolAuxSectionDefinitionPtr(coff: *Coff, si: Symbol.Index) *align(2) std.coff.SectionDefinition { - const sti = coff.symbol_table.entries.get(si).?.index; - + const sti = coff.symbol_table.entries.get(si).?.sti; const symbol = coff.symbolTableEntryPtr(sti); assert(symbol.storage_class == .STATIC and symbol.number_of_aux_symbols == 1); - return @ptrCast(symbolTableEntryPtr(coff, @enumFromInt(@intFromEnum(sti) + 1))); + return @ptrCast(@alignCast( + &Node.known.symbol_table.slice(&coff.mf)[(sti.unwrap().? + 1) * std.coff.Symbol.sizeOf()], + )); } pub fn symbolTableStringLenPtr(coff: *Coff) *align(2) u32 { @@ -1772,6 +1843,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, + .sti = .none, }; return @enumFromInt(coff.symbols.items.len); } @@ -1808,27 +1880,22 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { return @enumFromInt(gop.key_ptr.*); } -pub fn globalSymbol(coff: *Coff, name: []const u8, lib_name: ?[]const u8) !Symbol.Index { - return (try getOrPutGlobalSymbol(coff, name, lib_name)).value_ptr.*; -} - -fn getOrPutGlobalSymbol( - coff: *Coff, +pub fn globalSymbol(coff: *Coff, opts: struct { name: []const u8, - lib_name: ?[]const u8, -) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult { + lib_name: ?[]const u8 = null, +}) !Symbol.Index { const gpa = coff.base.comp.gpa; try coff.symbols.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.globals.getOrPut(gpa, .{ - .name = try coff.getOrPutString(name), - .lib_name = try coff.getOrPutOptionalString(lib_name), + .name = try coff.getOrPutString(opts.name), + .lib_name = try coff.getOrPutOptionalString(opts.lib_name), }); if (!sym_gop.found_existing) { sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); coff.synth_prog_node.increaseEstimatedTotalItems(1); } - return sym_gop; + return sym_gop.value_ptr.*; } fn navSection( @@ -1870,10 +1937,10 @@ fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.Na pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index { const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol( - @"extern".name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ); + if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{ + .name = @"extern".name.toSlice(ip), + .lib_name = @"extern".lib_name.toSlice(ip), + }); const nmi = try coff.navMapIndex(zcu, nav_index); return nmi.symbol(coff); } @@ -1926,7 +1993,7 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. reloc_info.addend, switch (coff.targetLoad(&coff.headerPtr().machine)) { else => unreachable, - .AMD64 => .{ .AMD64 = .ADDR64 }, + .AMD64 => .{ .AMD64 = .ADDR64 }, // TODO: Switch to REL32 for obj/archive .I386 => .{ .I386 = .DIR32 }, }, ); @@ -1941,7 +2008,7 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: Member.Kind, size: usize) !Member. const comp = coff.base.comp; const gpa = comp.gpa; - // TODO: These two nodes could to be inside a movable node? Only if coff or import + // TODO: These two nodes could to be inside a movable node if kind == .coff|.import const header_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{ .size = @sizeOf(std.coff.ArchiveMemberHeader), @@ -1967,7 +2034,7 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: Member.Kind, size: usize) !Member. .kind = kind, .header_ni = header_ni, .content_ni = content_ni, - .symbol_offsets = .empty, + .first_linker_indices = .empty, }); coff.nodes.appendAssumeCapacity(.{ .archive_member_header = mi }); @@ -2028,9 +2095,9 @@ fn appendMemberSymbolString( name_slice[name.len] = 0; } -fn addMemberSymbol( +fn ensureMemberSymbol( coff: *Coff, - name: String, + name: []const u8, mi: Member.Index, si: Symbol.Index, ) !void { @@ -2038,66 +2105,69 @@ fn addMemberSymbol( const member = mi.get(coff); assert(member.kind == .coff); - const gop = try member.symbol_offsets.getOrPut(gpa, si); + const name_string = try coff.getOrPutString(name); + const gop = try member.first_linker_indices.getOrPut(gpa, si); if (gop.found_existing) return; - // TODO: Detect duplicate names (ie. a name used by a symbol in another member, not the zcu since those already go through globals) + // TODO: Detect duplicate names (ie. a name used by a symbol in another member, + // not the zcu since those already go through globals) - const symbol_index = blk: { + const mfli: Member.FirstLinkerIndex = blk: { const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr(); const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big); num_symbols_ptr.* = std.mem.nativeTo(u32, num_symbols + 1, .big); - break :blk num_symbols; + break :blk @enumFromInt(num_symbols); }; - gop.value_ptr.* = symbol_index; - const name_slice = name.toSlice(coff); + gop.value_ptr.* = mfli; // Linker member fields are not modeled as nodes because MappedFile // can't guarantee that they will be tightly packed after resizing - const new_string_table_size = coff.lib_string_len + name_slice.len + 1; + const new_string_table_size = coff.lib_string_len + name.len + 1; defer coff.lib_string_len = new_string_table_size; { - const old_header_size = @sizeOf(u32) + symbol_index * @sizeOf(u32); + const old_header_size = @sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u32); const new_header_size = old_header_size + @sizeOf(u32); try Node.known.first_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size); const slice = Node.known.first_linker_member.slice(&coff.mf); @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); - @memcpy(slice[new_header_size + coff.lib_string_len ..][0 .. name_slice.len + 1], name_slice[0 .. name_slice.len + 1]); + @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name.len], name[0..name.len]); + slice[new_header_size + coff.lib_string_len + name.len] = 0; // New offset entry is written in flushMember } { const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); - const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + symbol_index * @sizeOf(u16); + const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u16); const new_header_size = old_header_size + @sizeOf(u16); try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size); const needs_sort = if (coff.lib_string_table.items.len > 0) std.mem.lessThan( u8, - name_slice, + name, coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff), ) else false; - try coff.lib_string_table.append(gpa, name); + try coff.lib_string_table.append(gpa, name_string); const slice = Node.known.second_linker_member.slice(&coff.mf); const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..])); - coff.targetStore(num_symbols_ptr, symbol_index + 1); + coff.targetStore(num_symbols_ptr, @intFromEnum(mfli) + 1); if (needs_sort) { // The entire string table is rebuilt in flushMember after sorting coff.pending_members.putAssumeCapacity(Member.Index.second, {}); } else { @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); - @memcpy(slice[new_header_size + coff.lib_string_len ..][0 .. name_slice.len + 1], name_slice[0 .. name_slice.len + 1]); + @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name.len], name[0..name.len]); + slice[new_header_size + coff.lib_string_len + name.len] = 0; } // Indices in this table are 1-based @@ -2120,6 +2190,7 @@ fn addSymbolTableEntry( assert(!coff.isImage()); const gpa = coff.base.comp.gpa; + // TODO: Avoid geOrPutString if it fits (only need to actually make the String for adding member symbol) const string, const name_slice = switch (name) { .bytes => |bytes| .{ try coff.getOrPutString(bytes), bytes }, .string => |s| .{ s, s.toSlice(coff) }, @@ -2140,11 +2211,18 @@ fn addSymbolTableEntry( break :index .{ .long = string_gop.value_ptr.* }; } else .{ .short = name_slice }; - const symbol_index = coff.targetLoad(&coff.headerPtr().number_of_symbols); + const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); + + const sym = si.get(coff); + sym.sti = .wrap(old_num_symbols); + si.updateRelocsSymbolTableIndex(coff); + + log.debug("addSymbolTableEntry({s}, {d}) = {d}", .{ name_slice, si, sym.sti.unwrap().? }); + + // TODO: Can look at sym.ni to know what kind this is + const symbols_added: u8 = switch (add) { .section => count: { - const sym = si.get(coff); - try coff.nodes.ensureUnusedCapacity(gpa, 2); _ = try coff.addSymbolTableEntryAssumeCapacity( symbol_name, @@ -2159,28 +2237,32 @@ fn addSymbolTableEntry( ); // Aux entry ields are updated by flushMoved / flushResized - try coff.symbol_table.entries.put(gpa, si, .{ .entry_si = .null, - .index = @enumFromInt(symbol_index), + .sti = sym.sti, }); break :count 2; }, - .global => |global| count: { - const sym = si.get(coff); - + .global => count: { try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); + if (sym.ni != .none) { + try coff.ensureMemberSymbol( + name_slice, // TODO: Swap to string? + coff.getNode(Node.known.zcu_member).archive_member, + si, + ); + } + const entry_ni = try coff.addSymbolTableEntryAssumeCapacity( symbol_name, - if (global.import) 0 else coff.computeNodeSectionOffset(sym.ni), - if (global.import) .UNDEFINED else sym.section_number, + if (sym.ni == .none) 0 else coff.computeNodeSectionOffset(sym.ni), + sym.section_number, .{ .base_type = .NULL, - .complex_type = if (global.import or - Symbol.Index.text.get(coff).section_number == sym.section_number) + .complex_type = if (Symbol.Index.text.get(coff).section_number == sym.section_number) .FUNCTION else .NULL, @@ -2198,24 +2280,27 @@ fn addSymbolTableEntry( entry_sym.section_number = .UNDEFINED; } - try coff.addReloc( - entry_si, - @offsetOf(std.coff.Symbol, "value"), - si, - 0, - .{ .AMD64 = .SECREL }, - ); + if (sym.ni != .none) { + // TODO: This serves to update the std.coff.Symbol.value (to VA of si), is this working? + try coff.addReloc( + entry_si, + @offsetOf(std.coff.Symbol, "value"), + si, + 0, + .{ .AMD64 = .SECREL }, // TODO: x86 too + ); + } try coff.symbol_table.entries.put(gpa, si, .{ .entry_si = entry_si, - .index = @enumFromInt(symbol_index), + .sti = sym.sti, }); break :count 1; }, }; - const new_num_symbols = symbol_index + symbols_added; + const new_num_symbols = old_num_symbols + symbols_added; coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); coff.symbol_table.pending_shrink = Node.known.symbol_table.location(&coff.mf).resolve(&coff.mf)[1] > @@ -2243,15 +2328,6 @@ fn addSymbolTableEntryAssumeCapacity( coff.nodes.appendAssumeCapacity(.symbol_table_entry); const entry: *align(2) std.coff.Symbol = @ptrCast(@alignCast(entry_ni.slice(&coff.mf))); - entry.* = .{ - .name = undefined, - .value = value, - .section_number = @enumFromInt(@intFromEnum(section_number)), - .type = @"type", - .storage_class = storage_class, - .number_of_aux_symbols = number_of_aux_symbols, - }; - switch (name) { .short => |s| { @memcpy(entry.name[0..s.len], s); @@ -2264,6 +2340,13 @@ fn addSymbolTableEntryAssumeCapacity( }, } + // TODO: Would be ideal to assign entry.*, but given @sizeOf() > entry.sizeOf(), is that valid? + entry.value = value; + entry.section_number = @enumFromInt(@intFromEnum(section_number)); + entry.type = @"type"; + entry.storage_class = storage_class; + entry.number_of_aux_symbols = number_of_aux_symbols; + if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.SectionHeader, entry.*); @@ -2310,7 +2393,10 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags }); const si = coff.addSymbolAssumeCapacity(); - coff.section_table.appendAssumeCapacity(si); + coff.section_table.appendAssumeCapacity(.{ + .si = si, + .relocation_table_ni = .none, + }); coff.nodes.appendAssumeCapacity(.{ .image_section = si }); const section_table = coff.sectionTableSlice(); @@ -2318,7 +2404,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags const virtual_size = coff.optionalHeaderField(.section_alignment); const rva: u32 = switch (section_index) { 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]), - else => coff.section_table.items[section_index - 1].get(coff).rva + + else => coff.section_table.items[section_index - 1].si.get(coff).rva + coff.targetLoad(§ion_table[section_index - 1].virtual_size), }; @@ -2456,6 +2542,35 @@ fn objectSectionMapIndex( return osmi; } +fn ensureUnusedRelocCapacity(coff: *Coff, loc_si: Symbol.Index, len: usize) !void { + const gpa = coff.base.comp.gpa; + + try coff.relocs.ensureUnusedCapacity(gpa, len); + if (isImage(coff)) return; + + switch (loc_si.get(coff).section_number) { + .UNDEFINED, .ABSOLUTE, .DEBUG => {}, + else => |sn| { + const section = sn.section(coff); + const header = sn.header(coff); + const new_size = (len + coff.targetLoad(&header.number_of_relocations)) * std.coff.Relocation.sizeOf(); + if (section.relocation_table_ni == .none) { + // The entry's length in the file is shorter than its @sizeOf + try coff.nodes.ensureUnusedCapacity(gpa, 1); + section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, Node.known.zcu_member, .{ + .size = new_size, + .alignment = .@"2", + .moved = true, + .resized = true, + }); + coff.nodes.appendAssumeCapacity(.{ .relocation_table = sn }); + } else { + try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); + } + }, + } +} + pub fn addReloc( coff: *Coff, loc_si: Symbol.Index, @@ -2464,16 +2579,80 @@ pub fn addReloc( addend: i64, @"type": Reloc.Type, ) !void { - const gpa = coff.base.comp.gpa; const target = target_si.get(coff); + log.debug("addReloc({d}@{d} + {d} -> {d}@{d} + {d})", .{ loc_si, loc_si.get(coff).section_number, offset, target_si, target_si.get(coff).section_number, addend }); + + try ensureUnusedRelocCapacity(coff, loc_si, 1); + + // TODO: The switch should be in an ensure capacity for reloc fn + + const sri: Section.RelocationIndex = if (isImage(coff)) + .none + else switch (loc_si.get(coff).section_number) { + .UNDEFINED, .ABSOLUTE, .DEBUG => .none, + else => |loc_sn| sri: { + const header = loc_sn.header(coff); + const old_num_relocations = coff.targetLoad(&header.number_of_relocations); + const new_num_relocations = old_num_relocations + 1; + coff.targetStore( + &header.number_of_relocations, + new_num_relocations, + ); + coff.targetStore( + &coff.symbolAuxSectionDefinitionPtr(loc_sn.symbol(coff)).number_of_relocations, + new_num_relocations, + ); + + const sri: Section.RelocationIndex = .wrap(old_num_relocations); + const entry = sri.entry(coff, loc_sn).?; + + entry.virtual_address = @intCast(offset); + switch (target.sti) { + .none => { + // TODO: Now is the moment when we know we need to add this to the symbol table + + // DEBUG + var iter = coff.globals.iterator(); + while (iter.next()) |kv| { + if (kv.value_ptr.* == target_si) { + log.warn("creating reloc but there is no symbol table entry yet `{s}` {d}!", .{ kv.key_ptr.name.toSlice(coff), target_si }); + break; + } + } else { + log.warn("creating reloc but there is no symbol table entry yet (not global) {d}!", .{target_si}); + } + // DEBUG + + // TODO: Check all relocs at the end and assert if some of sri == .none + entry.symbol_table_index = 0; + }, + else => |sti| { + entry.symbol_table_index = sti.unwrap().?; + }, + } + + // const reloc_type: Reloc.Type = switch (coff.targetLoad(&coff.headerPtr().machine)) { + // else => unreachableaddrelo, + // .AMD64 => .{ .AMD64 = .REL32 }, + // .I386 => .{ .I386 = .REL32 }, + // }; + + entry.type = @bitCast(@"type"); //@bitCast(reloc_type); + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFieldsAligned(std.coff.Relocation, .@"2", entry); + + break :sri sri; + }, + }; + const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len); - (try coff.relocs.addOne(gpa)).* = .{ + coff.relocs.addOneAssumeCapacity().* = .{ .type = @"type", .prev = .none, .next = target.target_relocs, .loc = loc_si, .target = target_si, - .unused = 0, + .sri = sri, .offset = offset, .addend = addend, }; @@ -2516,6 +2695,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde const nmi = try coff.navMapIndex(zcu, nav_index); const si = nmi.symbol(coff); + log.debug("updateNav({f}) = {d}", .{ nav.fqn.fmt(ip), si }); const ni = ni: { switch (si.get(coff).ni) { .none => { @@ -2530,7 +2710,13 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; - // TODO: Add symbol table entry + // if (!isImage(coff)) { + // try coff.addSymbolTableEntry( + // .{ .bytes = nav.fqn.toSlice(ip) }, + // si, + // .{ .global = .{ .external = false, .import = false } }, + // ); + // } }, else => si.deleteLocationRelocs(coff), } @@ -2660,6 +2846,14 @@ fn updateFuncInner( const sym = si.get(coff); sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; + // + // if (!isImage(coff)) { + // try coff.addSymbolTableEntry( + // .{ .bytes = nav.fqn.toSlice(ip) }, + // si, + // .{ .global = .{ .external = false, .import = false } }, + // ); + // } }, else => si.deleteLocationRelocs(coff), } @@ -2993,6 +3187,19 @@ fn flushUav( coff.nodes.appendAssumeCapacity(.{ .uav = umi }); sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; + + // if (!isImage(coff)) { + // var name: [12]u8 = undefined; + // var w = std.Io.Writer.fixed(&name); + // w.print("uav.{x}", .{umi}) catch unreachable; + // // TODO: This is a bit awkward, the symbol table requires a name, and we + // // need to be in the sym table to be the target of relocs + // try coff.addSymbolTableEntry( + // .{ .bytes = w.buffered() }, + // si, + // .{ .global = .{ .external = false, .import = false } }, + // ); + // } }, else => { if (si.get(coff).ni.alignment(&coff.mf).order(uav_align.toStdMem()).compare(.gte)) @@ -3028,21 +3235,14 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const comp = zcu.comp; const gpa = zcu.gpa; const gn = gmi.globalName(coff); + log.debug("flushGlobal({s}, {?s}) = {d}", .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), gmi.symbol(coff) }); if (!coff.isImage()) { - // TODO: What about data imports? - - // const si = gmi.symbol(coff); - // const sym = si.get(coff); - // sym.section_number = Symbol.Index.text.get(coff).section_number; - // assert(sym.loc_relocs == .none); - // sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - // - // try coff.addSymbolTableEntry( - // .{ .bytes = gn.name.toSlice(coff) }, - // si, - // .{ .global = .{ .import = true } }, - // ); + try coff.addSymbolTableEntry( + .{ .string = gn.name }, + gmi.symbol(coff), + .global, + ); return; } @@ -3268,8 +3468,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .data_directories, .section_table, .placeholder, - - .symbol_table_entry, + .symbol_table_entry, // TODO: Need to impl this for symbol table updates to work? .string_table, => if (!coff.isArchive()) unreachable, .symbol_table => { @@ -3278,6 +3477,13 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]), ); }, + .relocation_table => |sn| { + coff.targetStore( + &sn.header(coff).pointer_to_relocations, + @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]), + ); + }, + .relocation_table_entry => {}, .archive_member_header => |mi| { const member = mi.get(coff); switch (member.kind) { @@ -3439,7 +3645,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ), } - if (size > coff.section_table.items[0].get(coff).rva) try coff.virtualSlide( + if (size > coff.section_table.items[0].si.get(coff).rva) try coff.virtualSlide( 0, std.mem.alignForward( u32, @@ -3483,6 +3689,9 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { assert(!coff.isImage()); coff.targetStore(coff.symbolTableStringLenPtr(), @intCast(size)); }, + .relocation_table, + .relocation_table_entry, + => assert(!coff.isImage()), .image_section => |si| { const sym = si.get(coff); const section_index = sym.section_number.toIndex(); @@ -3587,8 +3796,8 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void { .coff => { const file_offset: u32 = @intCast(member.header_ni.fileLocation(&coff.mf, false).offset); const first_linker_offsets = coff.firstLinkerMemberOffsetsSlice(); - for (member.symbol_offsets.values()) |offset_index| - first_linker_offsets[offset_index] = std.mem.nativeTo(u32, file_offset, .big); + for (member.first_linker_indices.values()) |mfli| + first_linker_offsets[@intFromEnum(mfli)] = std.mem.nativeTo(u32, file_offset, .big); }, } } @@ -3631,12 +3840,12 @@ fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void { for ( coff.section_table.items[start_section_index..], coff.sectionTableSlice()[start_section_index..], - ) |section_si, *section| { - const section_sym = section_si.get(coff); + ) |*section, *header| { + const section_sym = section.si.get(coff); section_sym.rva = rva; - coff.targetStore(§ion.virtual_address, rva); + coff.targetStore(&header.virtual_address, rva); try section_sym.ni.childrenMoved(coff.base.comp.gpa, &coff.mf); - rva += coff.targetLoad(§ion.virtual_size); + rva += coff.targetLoad(&header.virtual_size); } switch (coff.optionalHeaderPtr()) { inline else => |optional_header| coff.targetStore( @@ -3690,8 +3899,10 @@ fn updateExportsInner( for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); const name = @"export".opts.name.toSlice(ip); - const symbol_gop = try coff.getOrPutGlobalSymbol(name, null); - const export_si = symbol_gop.value_ptr.*; + const export_si = try coff.globalSymbol(.{ + .name = name, + .lib_name = null, + }); const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; @@ -3707,20 +3918,6 @@ fn updateExportsInner( std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); } - if (coff.isArchive()) { - try coff.addMemberSymbol( - symbol_gop.key_ptr.*.name, - coff.getNode(Node.known.zcu_member).archive_member, - export_si, - ); - - try coff.addSymbolTableEntry( - .{ .bytes = name }, - export_si, - .{ .global = .{ .import = false } }, - ); - } - if (coff.export_table.ni == .none) continue; const entries_ctx = ExportTable.Adapter{ .coff = coff }; @@ -3809,7 +4006,7 @@ fn updateExportsInner( gop.value_ptr.si = export_si; const reloc = gop.value_ptr.*.export_address_table_ri.get(coff); reloc.target = export_si; - export_si.applyTargetRelocs(coff); + export_si.applyTargetRelocs(coff); // TODO: Potentially doing this twice, defer first one? } } } @@ -3818,6 +4015,9 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe _ = coff; _ = exported; _ = name; + + // TODO: Delete from first / second linker member table (remove swap?) + // TODO: Delete from symbol table inside section } fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void { -- 2.54.0 From 647fe54ef0843018bfc8416ae5f5f46e6bf2d78c Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 09/94] Coff: Writing relocations and symbol table --- lib/std/coff.zig | 4 + src/link/Coff.zig | 640 ++++++++++++++++++++-------------------- src/link/MappedFile.zig | 6 +- 3 files changed, 325 insertions(+), 325 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 904e5be36ce74776b5cdc833c93936ae4475655a..9ae5a4d67cebfea9cdfe3dc19dddec3e690f6487 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -1392,6 +1392,10 @@ pub const Relocation = extern struct { virtual_address: u32, symbol_table_index: u32, type: u16, + + pub fn sizeOf() usize { + return 10; + } }; pub const IMAGE = struct { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ee67523d09496db6fb570a8be730ec3fb74aee7d..3c5e86f38bbbf9f2878f830bf4ac36bae915c9e5 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -216,14 +216,26 @@ pub const Node = union(enum) { }; pub const GlobalMapIndex = enum(u32) { + none, _, + pub fn wrap(i: ?u32) GlobalMapIndex { + return @enumFromInt((i orelse return .none) + 1); + } + + pub fn unwrap(gmi: GlobalMapIndex) ?u32 { + return switch (gmi) { + .none => null, + _ => @intFromEnum(gmi) - 1, + }; + } + pub fn globalName(gmi: GlobalMapIndex, coff: *const Coff) GlobalName { - return coff.globals.keys()[@intFromEnum(gmi)]; + return coff.globals.keys()[gmi.unwrap().?]; } pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index { - return coff.globals.values()[@intFromEnum(gmi)]; + return coff.globals.values()[gmi.unwrap().?]; } }; @@ -452,32 +464,32 @@ pub const LongNamesTable = struct { }; pub const SymbolTable = struct { - string_offsets: std.AutoArrayHashMapUnmanaged(String, StringIndex), - entries: std.AutoArrayHashMapUnmanaged(Symbol.Index, Entry), + strings: std.AutoArrayHashMapUnmanaged(String, StringIndex), // Adding nodes to the symbol table has the result of accumulating padding - // between the last symbol and the string table, due to the growth factor - // in MappedFile. The spec requires the string table begin immediately - // after the last symbol, so we compact the symbol table node if needed. + // between the last symbol in the symbol table node and the start of the + // string table node, due to the growth factor in MappedFile. + // The spec requires the string table begin immediately after the last symbol, + // so we compact the symbol table node if needed. pending_shrink: bool, - pub const Entry = struct { - entry_si: Symbol.Index, - sti: Index, // TODO: Is this redundant now that we store it on symbol? - }; - pub const Add = union(enum) { section, global, }; + pub const StringIndex = enum(u32) { + _, + }; + pub const SymbolName = union(enum) { short: []const u8, long: StringIndex, }; - // Symbol.Index does not map 1:1 with SymbolTable.Index due to - // variable number of auxiliary entries that may trail each symbol + // Symbol.Index does not map 1:1 with SymbolTable.Index: + // - Not all symbols need a symbol table entry + // - A variable number of auxiliary entries may trail each symbol pub const Index = enum(u32) { none, _, @@ -492,14 +504,6 @@ pub const SymbolTable = struct { _ => @intFromEnum(sti) - 1, }; } - - pub fn get(sti: SymbolTable.Index, coff: *Coff) *Entry { - return &coff.symbol_table.entries.values()[sti.unwrap().?]; - } - }; - - pub const StringIndex = enum(u32) { - _, }; }; @@ -666,7 +670,7 @@ pub const Symbol = struct { target_relocs: Reloc.Index, section_number: SectionNumber, sti: SymbolTable.Index, - unused0: u32 = 0, + gmi: Node.GlobalMapIndex, unused1: u16 = 0, pub const SectionNumber = enum(i16) { @@ -718,9 +722,27 @@ pub const Symbol = struct { si.applyTargetRelocs(coff); } + pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { + const sym = si.get(coff); + const index = sym.sti.unwrap() orelse return; + var ri = sym.target_relocs; + while (ri != .none) { + const reloc = ri.get(coff); + assert(reloc.target == si); + if (reloc.sri.entry(coff, reloc.loc.get(coff).section_number)) |entry| + coff.targetStore(&entry.symbol_table_index, index); + ri = reloc.next; + } + } + pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) void { - for (coff.relocs.items[@intFromEnum(si.get(coff).loc_relocs)..]) |*reloc| { + const sym = si.get(coff); + for (coff.relocs.items[@intFromEnum(sym.loc_relocs)..]) |*reloc| { if (reloc.loc != si) break; + if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore( + &entry.virtual_address, + @intCast(coff.computeNodeSectionOffset(sym.ni) + reloc.offset), + ); reloc.apply(coff); } } @@ -743,17 +765,6 @@ pub const Symbol = struct { } sym.loc_relocs = .none; } - - pub fn updateRelocsSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { - const sym = si.get(coff); - var ri = sym.target_relocs; - while (ri != .none) { - const reloc = ri.get(coff); - if (reloc.sri.entry(coff, reloc.loc.get(coff).section_number)) |entry| - coff.targetStore(&entry.symbol_table_index, sym.sti.unwrap().?); - ri = reloc.next; - } - } }; comptime { @@ -798,20 +809,72 @@ pub const Reloc = extern struct { .none => return, else => |ni| if (ni.hasMoved(&coff.mf)) return, } + + const loc_slice = loc_sym.ni.slice(&coff.mf)[@intCast(reloc.offset)..]; + const target_endian = coff.targetEndian(); + + if (!coff.isImage()) { + switch (coff.targetLoad(&coff.headerPtr().machine)) { + else => |machine| @panic(@tagName(machine)), + .AMD64 => switch (reloc.type.AMD64) { + else => |kind| @panic(@tagName(kind)), + .ABSOLUTE => {}, + .ADDR64 => std.mem.writeInt( + u64, + loc_slice[0..8], + @intCast(reloc.addend), + target_endian, + ), + .ADDR32, + .ADDR32NB, + .REL32, + .REL32_1, + .REL32_2, + .REL32_3, + .REL32_4, + .REL32_5, + .SECREL, + => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(reloc.addend), + target_endian, + ), + }, + .I386 => switch (reloc.type.I386) { + else => |kind| @panic(@tagName(kind)), + .ABSOLUTE => {}, + .DIR16, + .REL16, + => std.mem.writeInt( + u16, + loc_slice[0..2], + @intCast(reloc.addend), + target_endian, + ), + .DIR32, + .DIR32NB, + .REL32, + .SECREL, + => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(reloc.addend), + target_endian, + ), + }, + } + + return; + } + const target_sym = reloc.target.get(coff); switch (target_sym.ni) { .none => return, else => |ni| if (ni.hasMoved(&coff.mf)) return, } - const loc_slice = loc_sym.ni.slice(&coff.mf)[@intCast(reloc.offset)..]; + const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend)); - const target_endian = coff.targetEndian(); - - // TODO: Is this right? - const base = if (coff.isImage()) - coff.optionalHeaderField(.image_base) - else - 0; // should be offset within section - take target_rva - section_rva (but section is 0!) switch (coff.targetLoad(&coff.headerPtr().machine)) { else => |machine| @panic(@tagName(machine)), @@ -821,13 +884,13 @@ pub const Reloc = extern struct { .ADDR64 => std.mem.writeInt( u64, loc_slice[0..8], - base + target_rva, + coff.optionalHeaderField(.image_base) + target_rva, target_endian, ), .ADDR32 => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(base + target_rva), + @intCast(coff.optionalHeaderField(.image_base) + target_rva), target_endian, ), .ADDR32NB => std.mem.writeInt( @@ -875,7 +938,7 @@ pub const Reloc = extern struct { .SECREL => std.mem.writeInt( u32, loc_slice[0..4], - coff.computeNodeSectionOffset(target_sym.ni), + @intCast(coff.computeNodeSectionOffset(target_sym.ni) + reloc.addend), target_endian, ), }, @@ -885,7 +948,7 @@ pub const Reloc = extern struct { .DIR16 => std.mem.writeInt( u16, loc_slice[0..2], - @intCast(base + target_rva), + @intCast(coff.optionalHeaderField(.image_base) + target_rva), target_endian, ), .REL16 => std.mem.writeInt( @@ -897,7 +960,7 @@ pub const Reloc = extern struct { .DIR32 => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(base + target_rva), + @intCast(coff.optionalHeaderField(.image_base) + target_rva), target_endian, ), .DIR32NB => std.mem.writeInt( @@ -915,7 +978,7 @@ pub const Reloc = extern struct { .SECREL => std.mem.writeInt( u32, loc_slice[0..4], - coff.computeNodeSectionOffset(target_sym.ni), + @intCast(coff.computeNodeSectionOffset(target_sym.ni) + reloc.addend), target_endian, ), }, @@ -923,7 +986,11 @@ pub const Reloc = extern struct { } pub fn delete(reloc: *Reloc, coff: *Coff) void { - // TODO: Need to remove this from the COFF relocation table (remove swap) + if (reloc.sri != .none) { + // TODO: Need to remove this from the COFF relocation table (maybe removeswap?) + // TODO: If this was the last reloc causing something to be in the symbol table, we should remove the sti + // That will require flushSymbolTableIndex on the swapped symbol if we exchange indices + } switch (reloc.prev) { .none => { @@ -1036,8 +1103,7 @@ fn create( .entries = .empty, }, .symbol_table = .{ - .string_offsets = .empty, - .entries = .empty, + .strings = .empty, .pending_shrink = false, }, .strings = .empty, @@ -1088,8 +1154,7 @@ pub fn deinit(coff: *Coff) void { coff.long_names_table.entries.deinit(gpa); coff.import_table.entries.deinit(gpa); coff.export_table.entries.deinit(gpa); - coff.symbol_table.string_offsets.deinit(gpa); - coff.symbol_table.entries.deinit(gpa); + coff.symbol_table.strings.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); coff.section_table.deinit(gpa); @@ -1159,8 +1224,8 @@ fn initHeaders( if (comp.zcu != null) { // Section nodes expected_nodes_len += 3; - // Symbol table nodes - if (is_archive) expected_nodes_len += 6; + // // Symbol table nodes + // if (is_archive) expected_nodes_len += 6; // Pseudo-sections and import / export table nodes if (is_image) expected_nodes_len += 9; // TLS section nodes @@ -1421,7 +1486,7 @@ fn initHeaders( })); coff.nodes.appendAssumeCapacity(.section_table); - // TODO: These two nodes could be inside one movable node + // TODO: These two nodes could be inside one movable node? const symbol_table_ni = Node.known.symbol_table; assert(symbol_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ .alignment = .@"2", @@ -1450,6 +1515,7 @@ fn initHeaders( .target_relocs = .none, .section_number = .UNDEFINED, .sti = .none, + .gmi = .none, }; assert(try coff.addSection(".data", .{ .CNT_INITIALIZED_DATA = true, @@ -1789,20 +1855,24 @@ pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader { )); } -pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) *align(2) std.coff.Symbol { - return @ptrCast(@alignCast( - &Node.known.symbol_table.slice(&coff.mf)[sti.unwrap().? * std.coff.Symbol.sizeOf()], - )); +pub fn symbolTableEntryStoragePtr(coff: *Coff, index: u32) *[std.coff.Symbol.sizeOf()]u8 { + assert(!coff.isImage()); + const offset = index * std.coff.Symbol.sizeOf(); + return @ptrCast(@alignCast(Node.known.symbol_table.slice(&coff.mf)[offset..][0..std.coff.Symbol.sizeOf()])); } -pub fn symbolAuxSectionDefinitionPtr(coff: *Coff, si: Symbol.Index) *align(2) std.coff.SectionDefinition { - const sti = coff.symbol_table.entries.get(si).?.sti; - const symbol = coff.symbolTableEntryPtr(sti); - assert(symbol.storage_class == .STATIC and symbol.number_of_aux_symbols == 1); +pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.Symbol { + if (sti.unwrap()) |index| + return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, index))) + else + return null; +} - return @ptrCast(@alignCast( - &Node.known.symbol_table.slice(&coff.mf)[(sti.unwrap().? + 1) * std.coff.Symbol.sizeOf()], - )); +pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) *align(2) std.coff.SectionDefinition { + const sti = si.get(coff).sti; + const entry = symbolTableEntryPtr(coff, sti).?; + assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1); + return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); } pub fn symbolTableStringLenPtr(coff: *Coff) *align(2) u32 { @@ -1844,6 +1914,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .target_relocs = .none, .section_number = .UNDEFINED, .sti = .none, + .gmi = .none, }; return @enumFromInt(coff.symbols.items.len); } @@ -1891,7 +1962,9 @@ pub fn globalSymbol(coff: *Coff, opts: struct { .lib_name = try coff.getOrPutOptionalString(opts.lib_name), }); if (!sym_gop.found_existing) { - sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); + const si = coff.addSymbolAssumeCapacity(); + si.get(coff).gmi = .wrap(@intCast(sym_gop.index)); + sym_gop.value_ptr.* = si; coff.synth_prog_node.increaseEstimatedTotalItems(1); } @@ -1993,7 +2066,7 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. reloc_info.addend, switch (coff.targetLoad(&coff.headerPtr().machine)) { else => unreachable, - .AMD64 => .{ .AMD64 = .ADDR64 }, // TODO: Switch to REL32 for obj/archive + .AMD64 => .{ .AMD64 = .ADDR64 }, .I386 => .{ .I386 = .DIR32 }, }, ); @@ -2097,7 +2170,7 @@ fn appendMemberSymbolString( fn ensureMemberSymbol( coff: *Coff, - name: []const u8, + name: String, mi: Member.Index, si: Symbol.Index, ) !void { @@ -2105,7 +2178,6 @@ fn ensureMemberSymbol( const member = mi.get(coff); assert(member.kind == .coff); - const name_string = try coff.getOrPutString(name); const gop = try member.first_linker_indices.getOrPut(gpa, si); if (gop.found_existing) return; @@ -2124,7 +2196,8 @@ fn ensureMemberSymbol( // Linker member fields are not modeled as nodes because MappedFile // can't guarantee that they will be tightly packed after resizing - const new_string_table_size = coff.lib_string_len + name.len + 1; + const name_slice = name.toSlice(coff); + const new_string_table_size = coff.lib_string_len + name_slice.len + 1; defer coff.lib_string_len = new_string_table_size; { @@ -2134,8 +2207,8 @@ fn ensureMemberSymbol( const slice = Node.known.first_linker_member.slice(&coff.mf); @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); - @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name.len], name[0..name.len]); - slice[new_header_size + coff.lib_string_len + name.len] = 0; + @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]); + slice[new_header_size + coff.lib_string_len + name_slice.len] = 0; // New offset entry is written in flushMember } @@ -2149,13 +2222,13 @@ fn ensureMemberSymbol( const needs_sort = if (coff.lib_string_table.items.len > 0) std.mem.lessThan( u8, - name, + name_slice, coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff), ) else false; - try coff.lib_string_table.append(gpa, name_string); + try coff.lib_string_table.append(gpa, name); const slice = Node.known.second_linker_member.slice(&coff.mf); const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..])); @@ -2166,8 +2239,8 @@ fn ensureMemberSymbol( coff.pending_members.putAssumeCapacity(Member.Index.second, {}); } else { @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); - @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name.len], name[0..name.len]); - slice[new_header_size + coff.lib_string_len + name.len] = 0; + @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]); + slice[new_header_size + coff.lib_string_len + name_slice.len] = 0; } // Indices in this table are 1-based @@ -2178,189 +2251,130 @@ fn ensureMemberSymbol( coff.pending_members.putAssumeCapacity(mi, {}); } -fn addSymbolTableEntry( - coff: *Coff, - name: union(enum) { - bytes: []const u8, - string: String, - }, - si: Symbol.Index, - add: SymbolTable.Add, -) !void { +// TODO: -> flushSymbolTableEntry, and push all call sites onto a pending list instead? +fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { assert(!coff.isImage()); const gpa = coff.base.comp.gpa; - // TODO: Avoid geOrPutString if it fits (only need to actually make the String for adding member symbol) - const string, const name_slice = switch (name) { - .bytes => |bytes| .{ try coff.getOrPutString(bytes), bytes }, - .string => |s| .{ s, s.toSlice(coff) }, - }; - - const symbol_name: SymbolTable.SymbolName = if (name_slice.len > 8) index: { - const string_gop = try coff.symbol_table.string_offsets.getOrPut(gpa, string); - if (!string_gop.found_existing) { - const string_index = Node.known.string_table.location(&coff.mf).resolve(&coff.mf)[1]; - string_gop.value_ptr.* = @enumFromInt(string_index); - - try Node.known.string_table.resize(&coff.mf, gpa, string_index + name_slice.len + 1); - const slice = Node.known.string_table.slice(&coff.mf); - @memcpy(slice[string_index..][0..name_slice.len], name_slice); - slice[string_index + name_slice.len] = 0; - } - - break :index .{ .long = string_gop.value_ptr.* }; - } else .{ .short = name_slice }; - - const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); - const sym = si.get(coff); - sym.sti = .wrap(old_num_symbols); - si.updateRelocsSymbolTableIndex(coff); - - log.debug("addSymbolTableEntry({s}, {d}) = {d}", .{ name_slice, si, sym.sti.unwrap().? }); - - // TODO: Can look at sym.ni to know what kind this is - - const symbols_added: u8 = switch (add) { - .section => count: { - try coff.nodes.ensureUnusedCapacity(gpa, 2); - _ = try coff.addSymbolTableEntryAssumeCapacity( - symbol_name, - 0, - sym.section_number, - .{ - .complex_type = .NULL, - .base_type = .NULL, - }, - .STATIC, - 1, - ); - - // Aux entry ields are updated by flushMoved / flushResized - try coff.symbol_table.entries.put(gpa, si, .{ - .entry_si = .null, - .sti = sym.sti, - }); - - break :count 2; - }, - .global => count: { - try coff.nodes.ensureUnusedCapacity(gpa, 1); - try coff.symbols.ensureUnusedCapacity(gpa, 1); - - if (sym.ni != .none) { - try coff.ensureMemberSymbol( - name_slice, // TODO: Swap to string? - coff.getNode(Node.known.zcu_member).archive_member, - si, - ); - } - - const entry_ni = try coff.addSymbolTableEntryAssumeCapacity( - symbol_name, - if (sym.ni == .none) 0 else coff.computeNodeSectionOffset(sym.ni), - sym.section_number, - .{ - .base_type = .NULL, - .complex_type = if (Symbol.Index.text.get(coff).section_number == sym.section_number) + const has_node = sym.ni != .none; + assert(has_node or sym.gmi != .none); + + const entry = coff.symbolTableEntryPtr(sym.sti) orelse entry: { + var buf: [15]u8 = undefined; + const name_slice, const opt_name_string, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = + if (sym.gmi != .none) blk: { + const gn = sym.gmi.globalName(coff); + break :blk .{ + gn.name.toSlice(coff), + gn.name, + 0, + if (Symbol.Index.text.get(coff).section_number == sym.section_number) .FUNCTION else .NULL, + }; + } else switch (coff.getNode(sym.ni)) { + .image_section => .{ + &sym.section_number.header(coff).name, + null, + 1, + .NULL, }, - .EXTERNAL, - 0, - ); - - const entry_si = coff.addSymbolAssumeCapacity(); - { - const entry_sym = entry_si.get(coff); - entry_sym.ni = entry_ni; - assert(entry_sym.loc_relocs == .none); - entry_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - entry_sym.section_number = .UNDEFINED; - } + .nav => |nmi| blk: { + const zcu = coff.base.comp.zcu.?; + const ip = &zcu.intern_pool; + const nav = ip.getNav(nmi.navIndex(coff)); + break :blk .{ + nav.fqn.toSlice(ip), + null, + 0, + if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL, + }; + }, + .uav => |umi| blk: { + var w = Io.Writer.fixed(&buf); + w.print("__anon_{x}", .{umi.uavValue(coff)}) catch unreachable; + break :blk .{ w.buffered(), null, 0, .NULL }; + }, + else => { + log.err("TODO implement symbol table init for {s}", .{@tagName(coff.getNode(sym.ni))}); + return .none; + }, + }; + + const symbol_name: SymbolTable.SymbolName = if (name_slice.len > 8) name: { + const string = opt_name_string orelse try coff.getOrPutString(name_slice); + const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string); + if (!string_gop.found_existing) { + const string_index = Node.known.string_table.location(&coff.mf).resolve(&coff.mf)[1]; + string_gop.value_ptr.* = @enumFromInt(string_index); - if (sym.ni != .none) { - // TODO: This serves to update the std.coff.Symbol.value (to VA of si), is this working? - try coff.addReloc( - entry_si, - @offsetOf(std.coff.Symbol, "value"), - si, - 0, - .{ .AMD64 = .SECREL }, // TODO: x86 too - ); + try Node.known.string_table.resize(&coff.mf, gpa, string_index + name_slice.len + 1); + const slice = Node.known.string_table.slice(&coff.mf); + @memcpy(slice[string_index..][0..name_slice.len], name_slice); + slice[string_index + name_slice.len] = 0; } - try coff.symbol_table.entries.put(gpa, si, .{ - .entry_si = entry_si, - .sti = sym.sti, - }); + break :name .{ .long = string_gop.value_ptr.* }; + } else .{ .short = name_slice }; + + const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); + const new_num_symbols = old_num_symbols + 1 + num_aux_symbols; + + try Node.known.symbol_table.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); + + const symbol_table_loc = Node.known.symbol_table.location(&coff.mf).resolve(&coff.mf); + const string_table_loc = Node.known.string_table.location(&coff.mf).resolve(&coff.mf); + coff.symbol_table.pending_shrink = string_table_loc[0] - (symbol_table_loc[0] + symbol_table_loc[1]) > 0; + + coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); + sym.sti = .wrap(old_num_symbols); + si.flushSymbolTableIndex(coff); + + const entry = coff.symbolTableEntryPtr(sym.sti).?; + switch (symbol_name) { + .short => |s| { + @memcpy(entry.name[0..s.len], s); + @memset(entry.name[s.len..], 0); + }, + .long => |l| { + @memset(entry.name[0..4], 0); + const offset_ptr: *align(2) u32 = @ptrCast(entry.name[4..]); + coff.targetStore(offset_ptr, @intFromEnum(l)); + }, + } + + entry.section_number = @enumFromInt(@intFromEnum(sym.section_number)); + entry.type = .{ + .complex_type = complex_type, + .base_type = .NULL, + }; + entry.storage_class = if (sym.gmi == .none) .STATIC else .EXTERNAL; + entry.number_of_aux_symbols = num_aux_symbols; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry); + + for (1..num_aux_symbols + 1) |aux_index| + @memset(coff.symbolTableEntryStoragePtr(@intCast(old_num_symbols + aux_index)), 0); - break :count 1; - }, + break :entry entry; }; - const new_num_symbols = old_num_symbols + symbols_added; - coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); - coff.symbol_table.pending_shrink = - Node.known.symbol_table.location(&coff.mf).resolve(&coff.mf)[1] > - new_num_symbols * std.coff.Symbol.sizeOf(); -} - -/// Caller guarantees there is capacity for 1 + number_of_aux_symbols nodes. -/// Auxiliary nodes are zero-initialized. -fn addSymbolTableEntryAssumeCapacity( - coff: *Coff, - name: SymbolTable.SymbolName, - value: u32, - section_number: Symbol.SectionNumber, - @"type": std.coff.SymType, - storage_class: std.coff.StorageClass, - number_of_aux_symbols: u8, -) !MappedFile.Node.Index { - const gpa = coff.base.comp.gpa; - - const entry_ni = try coff.mf.addLastChildNode(gpa, Node.known.symbol_table, .{ - .alignment = .@"2", - .size = std.coff.Symbol.sizeOf(), - .fixed = true, + coff.targetStore(&entry.value, switch (sym.section_number) { + .UNDEFINED => sym.size, + .ABSOLUTE, + .DEBUG, + => unreachable, + else => switch (coff.getNode(sym.ni)) { + .image_section => 0, + else => coff.computeNodeSectionOffset(sym.ni), + }, }); - coff.nodes.appendAssumeCapacity(.symbol_table_entry); - const entry: *align(2) std.coff.Symbol = @ptrCast(@alignCast(entry_ni.slice(&coff.mf))); - switch (name) { - .short => |s| { - @memcpy(entry.name[0..s.len], s); - @memset(entry.name[s.len..], 0); - }, - .long => |l| { - @memset(entry.name[0..4], 0); - const offset_ptr: *align(2) u32 = @ptrCast(entry.name[4..]); - coff.targetStore(offset_ptr, @intFromEnum(l)); - }, - } + log.debug("updateSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); - // TODO: Would be ideal to assign entry.*, but given @sizeOf() > entry.sizeOf(), is that valid? - entry.value = value; - entry.section_number = @enumFromInt(@intFromEnum(section_number)); - entry.type = @"type"; - entry.storage_class = storage_class; - entry.number_of_aux_symbols = number_of_aux_symbols; - - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.SectionHeader, entry.*); - - for (0..number_of_aux_symbols) |_| { - const aux_ni = try coff.mf.addLastChildNode(gpa, Node.known.symbol_table, .{ - .alignment = .@"2", - .size = std.coff.Symbol.sizeOf(), - .fixed = true, - }); - coff.nodes.appendAssumeCapacity(.symbol_table_entry); - @memset(aux_ni.slice(&coff.mf), 0); - } - - return entry_ni; + return sym.sti; } fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index { @@ -2443,7 +2457,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags ), } } else { - try coff.addSymbolTableEntry(.{ .bytes = name }, si, .section); + assert(try coff.updateSymbolTableEntry(si) != .none); } return si; @@ -2579,67 +2593,66 @@ pub fn addReloc( addend: i64, @"type": Reloc.Type, ) !void { + const gpa = coff.base.comp.gpa; const target = target_si.get(coff); - log.debug("addReloc({d}@{d} + {d} -> {d}@{d} + {d})", .{ loc_si, loc_si.get(coff).section_number, offset, target_si, target_si.get(coff).section_number, addend }); - try ensureUnusedRelocCapacity(coff, loc_si, 1); + log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d})", .{ loc_si, loc_si.get(coff).section_number, offset, target_si, target_si.get(coff).section_number, addend }); - // TODO: The switch should be in an ensure capacity for reloc fn + try coff.relocs.ensureUnusedCapacity(gpa, 1); const sri: Section.RelocationIndex = if (isImage(coff)) .none else switch (loc_si.get(coff).section_number) { - .UNDEFINED, .ABSOLUTE, .DEBUG => .none, + .UNDEFINED, + .ABSOLUTE, + .DEBUG, + => .none, else => |loc_sn| sri: { + // The target may not have a node yet, or it could be an extern that will never + // have a node. In that case, flushGlobal will create the symbol table entry. + const sti: SymbolTable.Index = if (target.sti != .none) + target.sti + else if (target.ni != .none) + try updateSymbolTableEntry(coff, target_si) + else + .none; + + const section = loc_sn.section(coff); const header = loc_sn.header(coff); const old_num_relocations = coff.targetLoad(&header.number_of_relocations); const new_num_relocations = old_num_relocations + 1; + const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); + if (section.relocation_table_ni == .none) { + try coff.nodes.ensureUnusedCapacity(gpa, 1); + section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, Node.known.zcu_member, .{ + .size = new_size, + .alignment = .@"2", + .moved = true, + .resized = true, + }); + coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn }); + } else { + try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); + } + coff.targetStore( &header.number_of_relocations, new_num_relocations, ); coff.targetStore( - &coff.symbolAuxSectionDefinitionPtr(loc_sn.symbol(coff)).number_of_relocations, + &coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff)).number_of_relocations, new_num_relocations, ); + // TODO: These need to allocate from a free list (once deleting relocs is supported) (or can we just remove swap?) + const sri: Section.RelocationIndex = .wrap(old_num_relocations); const entry = sri.entry(coff, loc_sn).?; + if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index); - entry.virtual_address = @intCast(offset); - switch (target.sti) { - .none => { - // TODO: Now is the moment when we know we need to add this to the symbol table - - // DEBUG - var iter = coff.globals.iterator(); - while (iter.next()) |kv| { - if (kv.value_ptr.* == target_si) { - log.warn("creating reloc but there is no symbol table entry yet `{s}` {d}!", .{ kv.key_ptr.name.toSlice(coff), target_si }); - break; - } - } else { - log.warn("creating reloc but there is no symbol table entry yet (not global) {d}!", .{target_si}); - } - // DEBUG - - // TODO: Check all relocs at the end and assert if some of sri == .none - entry.symbol_table_index = 0; - }, - else => |sti| { - entry.symbol_table_index = sti.unwrap().?; - }, - } - - // const reloc_type: Reloc.Type = switch (coff.targetLoad(&coff.headerPtr().machine)) { - // else => unreachableaddrelo, - // .AMD64 => .{ .AMD64 = .REL32 }, - // .I386 => .{ .I386 = .REL32 }, - // }; - - entry.type = @bitCast(@"type"); //@bitCast(reloc_type); - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFieldsAligned(std.coff.Relocation, .@"2", entry); + // applyLocationRelocs updates `virtual_address` + // flushSymbolTableIndex updates `symbol_table_index` + coff.targetStore(&entry.type, @bitCast(@"type")); break :sri sri; }, @@ -2709,20 +2722,15 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde const sym = si.get(coff); sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; - - // if (!isImage(coff)) { - // try coff.addSymbolTableEntry( - // .{ .bytes = nav.fqn.toSlice(ip) }, - // si, - // .{ .global = .{ .external = false, .import = false } }, - // ); - // } }, else => si.deleteLocationRelocs(coff), } const sym = si.get(coff); assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + if (sym.target_relocs != .none) + _ = try coff.updateSymbolTableEntry(si); + break :ni sym.ni; }; @@ -2744,6 +2752,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde si.applyLocationRelocs(coff); } + // TODO: Did my MappedFile resize change affect this? if (nav.resolved.?.@"linksection".unwrap()) |_| { try ni.resize(&coff.mf, gpa, si.get(coff).size); var parent_ni = ni; @@ -2846,20 +2855,14 @@ fn updateFuncInner( const sym = si.get(coff); sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; - // - // if (!isImage(coff)) { - // try coff.addSymbolTableEntry( - // .{ .bytes = nav.fqn.toSlice(ip) }, - // si, - // .{ .global = .{ .external = false, .import = false } }, - // ); - // } }, else => si.deleteLocationRelocs(coff), } const sym = si.get(coff); assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + if (sym.target_relocs != .none) + _ = try coff.updateSymbolTableEntry(si); break :ni sym.ni; }; @@ -3037,7 +3040,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } if (coff.global_pending_index < coff.globals.count()) { const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid }; - const gmi: Node.GlobalMapIndex = @enumFromInt(coff.global_pending_index); + const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index); coff.global_pending_index += 1; const sub_prog_node = coff.synth_prog_node.start( gmi.globalName(coff).name.toSlice(coff), @@ -3187,19 +3190,6 @@ fn flushUav( coff.nodes.appendAssumeCapacity(.{ .uav = umi }); sym.ni = ni; sym.section_number = sec_si.get(coff).section_number; - - // if (!isImage(coff)) { - // var name: [12]u8 = undefined; - // var w = std.Io.Writer.fixed(&name); - // w.print("uav.{x}", .{umi}) catch unreachable; - // // TODO: This is a bit awkward, the symbol table requires a name, and we - // // need to be in the sym table to be the target of relocs - // try coff.addSymbolTableEntry( - // .{ .bytes = w.buffered() }, - // si, - // .{ .global = .{ .external = false, .import = false } }, - // ); - // } }, else => { if (si.get(coff).ni.alignment(&coff.mf).order(uav_align.toStdMem()).compare(.gte)) @@ -3210,6 +3200,9 @@ fn flushUav( const sym = si.get(coff); assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + if (sym.target_relocs != .none) + _ = try coff.updateSymbolTableEntry(si); + break :ni sym.ni; }; @@ -3238,11 +3231,14 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { log.debug("flushGlobal({s}, {?s}) = {d}", .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), gmi.symbol(coff) }); if (!coff.isImage()) { - try coff.addSymbolTableEntry( - .{ .string = gn.name }, - gmi.symbol(coff), - .global, - ); + const si = gmi.symbol(coff); + assert(try coff.updateSymbolTableEntry(si) != .none); + if (si.get(coff).ni != .none) + try coff.ensureMemberSymbol( + gn.name, + coff.getNode(Node.known.zcu_member).archive_member, + si, + ); return; } @@ -3707,9 +3703,9 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { try coff.virtualSlide(section_index + 1, sym.rva + virtual_size); } - if (coff.isArchive()) { + if (!coff.isImage()) { coff.targetStore( - &coff.symbolAuxSectionDefinitionPtr(si).length, + &coff.symbolTableSectionAuxEntryPtr(si).length, @intCast(size), ); } diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 91eeaa2cdf83552e1659173825d1a4eda6a4bf5b..ecb3d892e6dafd0861d288d0fc7a2e46f7ba41ec 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -696,7 +696,7 @@ fn shrinkNode( gpa: std.mem.Allocator, ni: Node.Index, size: u64, - shrink_next: bool, + shift_next: bool, ) !void { const node = ni.get(mf); const old_offset, _ = node.location().resolve(mf); @@ -714,7 +714,7 @@ fn shrinkNode( try mf.updates.ensureUnusedCapacity(gpa, 2); ni.setLocationAssumeCapacity(mf, old_offset, size); - if (!shrink_next or node.next == .none) return; + if (!shift_next or node.next == .none) return; const next = node.next.get(mf); const old_next_offset, const next_size = next.location().resolve(mf); @@ -738,7 +738,7 @@ 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; + //if (new_size <= old_size) return; // Resize the entire file if (ni == Node.Index.root) { -- 2.54.0 From 0bfa6e41e9654d35d79ce4b617180a86eb5a2c3d Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 10/94] Coff: More progress on archives - Move flushing symbol table entries to an idle task - Add progress nodes - Wire up --debug-link-snapshot --- src/link/Coff.zig | 360 +++++++++++++++++++++++++++++----------------- 1 file changed, 225 insertions(+), 135 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 3c5e86f38bbbf9f2878f830bf4ac36bae915c9e5..1d69d5f9d7726164487fe15ee098fc8c0cb29389 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -39,6 +39,7 @@ strings: std.HashMapUnmanaged( ), string_bytes: std.ArrayList(u8), section_table: std.ArrayList(Section), +tls_si: Symbol.Index, pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), symbols: std.ArrayList(Symbol), @@ -56,6 +57,9 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct { relocs: std.ArrayList(Reloc), const_prog_node: std.Progress.Node, synth_prog_node: std.Progress.Node, +symbol_prog_node: std.Progress.Node, +member_prog_node: std.Progress.Node, +dump_snapshot: bool, pub const default_file_alignment: u16 = 0x200; pub const default_size_of_stack_reserve: u32 = 0x1000000; @@ -158,7 +162,6 @@ pub const Node = union(enum) { section_table, // Archives and objects only symbol_table, - symbol_table_entry, // Archives and objects only string_table, // Archives and objects only @@ -314,8 +317,6 @@ pub const Node = union(enum) { optional_header, data_directories, section_table, - symbol_table, - string_table, }; var mut_known: std.enums.EnumFieldStruct(Known, MappedFile.Node.Index, null) = undefined; const info = @typeInfo(Known).@"enum"; @@ -464,20 +465,18 @@ pub const LongNamesTable = struct { }; pub const SymbolTable = struct { + ni: MappedFile.Node.Index, + strings_ni: MappedFile.Node.Index, strings: std.AutoArrayHashMapUnmanaged(String, StringIndex), + pending: std.AutoArrayHashMapUnmanaged(Symbol.Index, void), - // Adding nodes to the symbol table has the result of accumulating padding + // Resizing the symbol table node has the result of accumulating padding // between the last symbol in the symbol table node and the start of the - // string table node, due to the growth factor in MappedFile. + // string table node, due to the shifting method when resizing the parent in MappedFile. // The spec requires the string table begin immediately after the last symbol, - // so we compact the symbol table node if needed. + // so we compact the symbol table node and move the string table back if needed. pending_shrink: bool, - pub const Add = union(enum) { - section, - global, - }; - pub const StringIndex = enum(u32) { _, }; @@ -1103,12 +1102,16 @@ fn create( .entries = .empty, }, .symbol_table = .{ + .ni = .none, + .strings_ni = .none, .strings = .empty, + .pending = .empty, .pending_shrink = false, }, .strings = .empty, .string_bytes = .empty, .section_table = .empty, + .tls_si = .null, .pseudo_section_table = .empty, .object_section_table = .empty, .symbols = .empty, @@ -1124,6 +1127,9 @@ fn create( .relocs = .empty, .const_prog_node = .none, .synth_prog_node = .none, + .symbol_prog_node = .none, + .member_prog_node = .none, + .dump_snapshot = options.enable_link_snapshots, }; errdefer coff.deinit(); @@ -1155,6 +1161,7 @@ pub fn deinit(coff: *Coff) void { coff.import_table.entries.deinit(gpa); coff.export_table.entries.deinit(gpa); coff.symbol_table.strings.deinit(gpa); + coff.symbol_table.pending.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); coff.section_table.deinit(gpa); @@ -1222,14 +1229,21 @@ fn initHeaders( var expected_nodes_len: usize = Node.known_count; if (comp.zcu != null) { - // Section nodes + // Sections expected_nodes_len += 3; - // // Symbol table nodes - // if (is_archive) expected_nodes_len += 6; - // Pseudo-sections and import / export table nodes - if (is_image) expected_nodes_len += 9; - // TLS section nodes - expected_nodes_len += @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2; + + if (is_image) + // Pseudo-sections and import / export table + expected_nodes_len += 9 + else + // Symbol table + expected_nodes_len += 2; + + // TLS section + if (comp.config.any_non_single_threaded) { + if (!is_image) expected_nodes_len += 1; + expected_nodes_len += 2; + } } defer assert(coff.nodes.len == expected_nodes_len); @@ -1486,26 +1500,26 @@ fn initHeaders( })); coff.nodes.appendAssumeCapacity(.section_table); - // TODO: These two nodes could be inside one movable node? - const symbol_table_ni = Node.known.symbol_table; - assert(symbol_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ - .alignment = .@"2", - .fixed = true, - .moved = true, - })); - coff.nodes.appendAssumeCapacity(.symbol_table); - - const string_table_ni = Node.known.string_table; - assert(string_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ - .alignment = .@"2", - .size = if (!is_image) @sizeOf(u32) else 0, - .fixed = true, - .resized = true, - })); - coff.nodes.appendAssumeCapacity(.string_table); - assert(coff.nodes.len == Node.known_count); + if (!is_image) { + // TODO: These two nodes could be inside one movable node? + coff.symbol_table.ni = try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + .alignment = .@"2", + .fixed = true, + .moved = true, + }); + coff.nodes.appendAssumeCapacity(.symbol_table); + + coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + .alignment = .@"2", + .size = @sizeOf(u32), + .fixed = true, + .resized = true, + }); + coff.nodes.appendAssumeCapacity(.string_table); + } + try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, @@ -1618,12 +1632,23 @@ fn initHeaders( std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, export_directory_table); } - // While tls variables allocated at runtime are writable, the template itself is not - if (comp.config.any_non_single_threaded) _ = try coff.objectSectionMapIndex( - .@".tls$", - if (is_image) coff.mf.flags.block_size else .@"1", - .{ .read = true }, - ); + if (comp.config.any_non_single_threaded) { + if (!is_image) + coff.tls_si = try coff.addSection(".tls$", .{ + .CNT_INITIALIZED_DATA = true, + .MEM_READ = true, + .MEM_WRITE = true, + }); + + // 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$", + coff.mf.flags.block_size, + .{ .read = true, .write = !is_image, .tls = true }, + ); + } } pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { @@ -1634,12 +1659,23 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index; break :count count; }); + if (!isImage(coff)) { + prog_node.increaseEstimatedTotalItems(2); + coff.symbol_prog_node = prog_node.start("Symbols", coff.symbol_table.pending.count()); + coff.member_prog_node = prog_node.start("Members", coff.pending_members.count()); + } coff.mf.update_prog_node = prog_node.start("Relocations", coff.mf.updates.items.len); } pub fn endProgress(coff: *Coff) void { coff.mf.update_prog_node.end(); coff.mf.update_prog_node = .none; + if (!isImage(coff)) { + coff.member_prog_node.end(); + coff.member_prog_node = .none; + coff.symbol_prog_node.end(); + coff.symbol_prog_node = .none; + } coff.synth_prog_node.end(); coff.synth_prog_node = .none; coff.const_prog_node.end(); @@ -1665,7 +1701,6 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .placeholder, .symbol_table, - .symbol_table_entry, .string_table, .relocation_table, .relocation_table_entry, @@ -1858,7 +1893,7 @@ pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader { pub fn symbolTableEntryStoragePtr(coff: *Coff, index: u32) *[std.coff.Symbol.sizeOf()]u8 { assert(!coff.isImage()); const offset = index * std.coff.Symbol.sizeOf(); - return @ptrCast(@alignCast(Node.known.symbol_table.slice(&coff.mf)[offset..][0..std.coff.Symbol.sizeOf()])); + return @ptrCast(@alignCast(coff.symbol_table.ni.slice(&coff.mf)[offset..][0..std.coff.Symbol.sizeOf()])); } pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.Symbol { @@ -1876,7 +1911,7 @@ pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) *align(2) st } pub fn symbolTableStringLenPtr(coff: *Coff) *align(2) u32 { - return @ptrCast(@alignCast(Node.known.string_table.slice(&coff.mf)[0..@sizeOf(u32)])); + return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)])); } pub fn importDirectoryTableSlice(coff: *Coff) []std.coff.ImportDirectoryEntry { @@ -1971,6 +2006,18 @@ pub fn globalSymbol(coff: *Coff, opts: struct { return sym_gop.value_ptr.*; } +pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void { + assert(!coff.isImage()); + const sym = si.get(coff); + assert(sym.ni != .none or sym.gmi != .none); + + const gpa = coff.base.comp.gpa; + const pending_gop = try coff.symbol_table.pending.getOrPut(gpa, si); + if (!pending_gop.found_existing) { + coff.symbol_prog_node.increaseEstimatedTotalItems(1); + } +} + fn navSection( coff: *Coff, zcu: *Zcu, @@ -1979,7 +2026,7 @@ fn navSection( const ip = &zcu.intern_pool; const default: String, const attributes: ObjectSectionAttributes = if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{ - .@".tls$", .{ .read = true, .write = true }, + .@".tls$", .{ .read = true, .write = true, .tls = true }, } else if (ip.isFunctionType(nav_resolved.type)) .{ .@".text", .{ .read = true, .execute = true }, } else if (nav_resolved.@"const") .{ @@ -2149,6 +2196,7 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: Member.Kind, size: usize) !Member. gpa, coff.pending_members.capacity() + 1, ); + coff.member_prog_node.increaseEstimatedTotalItems(1); }, } @@ -2249,16 +2297,15 @@ fn ensureMemberSymbol( } coff.pending_members.putAssumeCapacity(mi, {}); + coff.member_prog_node.increaseEstimatedTotalItems(1); } -// TODO: -> flushSymbolTableEntry, and push all call sites onto a pending list instead? -fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { +fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void { assert(!coff.isImage()); const gpa = coff.base.comp.gpa; const sym = si.get(coff); - const has_node = sym.ni != .none; - assert(has_node or sym.gmi != .none); + assert(sym.ni != .none or sym.gmi != .none); const entry = coff.symbolTableEntryPtr(sym.sti) orelse entry: { var buf: [15]u8 = undefined; @@ -2274,14 +2321,14 @@ fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { else .NULL, }; - } else switch (coff.getNode(sym.ni)) { + } else blk: switch (coff.getNode(sym.ni)) { .image_section => .{ &sym.section_number.header(coff).name, null, 1, .NULL, }, - .nav => |nmi| blk: { + .nav => |nmi| { const zcu = coff.base.comp.zcu.?; const ip = &zcu.intern_pool; const nav = ip.getNav(nmi.navIndex(coff)); @@ -2292,14 +2339,25 @@ fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL, }; }, - .uav => |umi| blk: { + .uav => |umi| { var w = Io.Writer.fixed(&buf); w.print("__anon_{x}", .{umi.uavValue(coff)}) catch unreachable; break :blk .{ w.buffered(), null, 0, .NULL }; }, + inline .lazy_code, .lazy_const_data => |mi, tag| { + const lazy_sym = mi.lazySymbol(coff); + const name = try std.fmt.allocPrint(gpa, "__lazy_{s}_{f}", .{ + @tagName(lazy_sym.kind), + Type.fromInterned(lazy_sym.ty).fmt(pt), + }); + defer gpa.free(name); + + const string = try coff.getOrPutString(name); + break :blk .{ string.toSlice(coff), string, 0, if (tag == .lazy_code) .FUNCTION else .NULL }; + }, else => { - log.err("TODO implement symbol table init for {s}", .{@tagName(coff.getNode(sym.ni))}); - return .none; + log.err("TODO implement symbol table init for {s} ({d})", .{ @tagName(coff.getNode(sym.ni)), si }); + unreachable; }, }; @@ -2307,11 +2365,11 @@ fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { const string = opt_name_string orelse try coff.getOrPutString(name_slice); const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string); if (!string_gop.found_existing) { - const string_index = Node.known.string_table.location(&coff.mf).resolve(&coff.mf)[1]; + const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1]; string_gop.value_ptr.* = @enumFromInt(string_index); - try Node.known.string_table.resize(&coff.mf, gpa, string_index + name_slice.len + 1); - const slice = Node.known.string_table.slice(&coff.mf); + try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name_slice.len + 1); + const slice = coff.symbol_table.strings_ni.slice(&coff.mf); @memcpy(slice[string_index..][0..name_slice.len], name_slice); slice[string_index + name_slice.len] = 0; } @@ -2322,11 +2380,7 @@ fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); const new_num_symbols = old_num_symbols + 1 + num_aux_symbols; - try Node.known.symbol_table.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); - - const symbol_table_loc = Node.known.symbol_table.location(&coff.mf).resolve(&coff.mf); - const string_table_loc = Node.known.string_table.location(&coff.mf).resolve(&coff.mf); - coff.symbol_table.pending_shrink = string_table_loc[0] - (symbol_table_loc[0] + symbol_table_loc[1]) > 0; + try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); sym.sti = .wrap(old_num_symbols); @@ -2373,8 +2427,6 @@ fn updateSymbolTableEntry(coff: *Coff, si: Symbol.Index) !SymbolTable.Index { }); log.debug("updateSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); - - return sym.sti; } fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index { @@ -2384,6 +2436,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.section_table.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); const coff_header = coff.headerPtr(); const section_index = coff.targetLoad(&coff_header.number_of_sections); @@ -2457,7 +2510,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags ), } } else { - assert(try coff.updateSymbolTableEntry(si) != .none); + try coff.pendingSymbolTableEntry(si); } return si; @@ -2472,7 +2525,9 @@ const ObjectSectionAttributes = packed struct { nocache: bool = false, discard: bool = false, remove: bool = false, + tls: bool = false, }; + fn pseudoSectionMapIndex( coff: *Coff, name: String, @@ -2485,6 +2540,8 @@ fn pseudoSectionMapIndex( if (!pseudo_section_gop.found_existing) { const parent: Symbol.Index = if (attributes.execute) .text + else if (attributes.tls and coff.tls_si != .null) + coff.tls_si else if (attributes.write) .data else @@ -2556,35 +2613,6 @@ fn objectSectionMapIndex( return osmi; } -fn ensureUnusedRelocCapacity(coff: *Coff, loc_si: Symbol.Index, len: usize) !void { - const gpa = coff.base.comp.gpa; - - try coff.relocs.ensureUnusedCapacity(gpa, len); - if (isImage(coff)) return; - - switch (loc_si.get(coff).section_number) { - .UNDEFINED, .ABSOLUTE, .DEBUG => {}, - else => |sn| { - const section = sn.section(coff); - const header = sn.header(coff); - const new_size = (len + coff.targetLoad(&header.number_of_relocations)) * std.coff.Relocation.sizeOf(); - if (section.relocation_table_ni == .none) { - // The entry's length in the file is shorter than its @sizeOf - try coff.nodes.ensureUnusedCapacity(gpa, 1); - section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, Node.known.zcu_member, .{ - .size = new_size, - .alignment = .@"2", - .moved = true, - .resized = true, - }); - coff.nodes.appendAssumeCapacity(.{ .relocation_table = sn }); - } else { - try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); - } - }, - } -} - pub fn addReloc( coff: *Coff, loc_si: Symbol.Index, @@ -2612,10 +2640,10 @@ pub fn addReloc( // have a node. In that case, flushGlobal will create the symbol table entry. const sti: SymbolTable.Index = if (target.sti != .none) target.sti - else if (target.ni != .none) - try updateSymbolTableEntry(coff, target_si) - else - .none; + else if (target.ni != .none) sti: { + try coff.pendingSymbolTableEntry(target_si); + break :sti .none; + } else .none; const section = loc_sn.section(coff); const header = loc_sn.header(coff); @@ -2714,6 +2742,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde .none => { const sec_si = try coff.navSection(zcu, nav.resolved.?); try coff.nodes.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ .alignment = zcu.navAlignment(nav_index).toStdMem(), .moved = true, @@ -2728,8 +2757,8 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde const sym = si.get(coff); assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - if (sym.target_relocs != .none) - _ = try coff.updateSymbolTableEntry(si); + if (!isImage(coff) and sym.target_relocs != .none) + try coff.pendingSymbolTableEntry(si); break :ni sym.ni; }; @@ -2836,6 +2865,7 @@ fn updateFuncInner( .none => { const sec_si = try coff.navSection(zcu, nav.resolved.?); try coff.nodes.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); const mod = zcu.navFileScope(func.owner_nav).mod.?; const target = &mod.resolved_target.result; const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ @@ -2861,8 +2891,8 @@ fn updateFuncInner( const sym = si.get(coff); assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - if (sym.target_relocs != .none) - _ = try coff.updateSymbolTableEntry(si); + if (!isImage(coff) and sym.target_relocs != .none) + try coff.pendingSymbolTableEntry(si); break :ni sym.ni; }; @@ -3015,8 +3045,9 @@ pub fn flush( else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), }; - coff.dumpStderr(tid) catch |err| - return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err}); + if (coff.dump_snapshot) + coff.dumpStderr(tid) catch |err| + return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err}); } pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { @@ -3083,6 +3114,29 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; }; + while (coff.symbol_table.pending.pop()) |pending_si| { + const sym = pending_si.key.get(coff); + const sub_prog_node = coff.idleProgNode( + tid, + coff.symbol_prog_node, + if (sym.ni != .none) + coff.getNode(sym.ni) + else + .{ .global = pending_si.key.get(coff).gmi }, + ); + defer sub_prog_node.end(); + coff.flushSymbolTableEntry( + pending_si.key, + .{ .zcu = comp.zcu.?, .tid = tid }, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return comp.link_diags.fail( + "linker failed to flush symbol table entry: {t}", + .{e}, + ), + }; + break :task; + } while (coff.mf.updates.pop()) |ni| { const clean_moved = ni.cleanMoved(&coff.mf); const clean_resized = ni.cleanResized(&coff.mf); @@ -3096,22 +3150,39 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } else coff.mf.update_prog_node.completeOne(); } while (coff.pending_members.pop()) |pending_mi| { - // TODO: Prog node + const sub_prog_node = coff.idleProgNode( + tid, + coff.symbol_prog_node, + coff.getNode(pending_mi.key.get(coff).content_ni), + ); + defer sub_prog_node.end(); try coff.flushMember(pending_mi.key); break :task; } + // TODO: This and the next task ideally only run once, as it's wasteful otherwise if (coff.export_table.pending_sort) { - // TODO: Prog node - coff.export_table.pending_sort = false; + defer coff.export_table.pending_sort = false; + const sub_prog_node = coff.idleProgNode( + tid, + coff.synth_prog_node, + coff.getNode(coff.export_table.ni), + ); + defer sub_prog_node.end(); + coff.flushExportsSort(); break :task; } - // TODO: This and the above task ideally run only once, as it's wasteful otherwise if (coff.symbol_table.pending_shrink) { - coff.symbol_table.pending_shrink = false; - // TODO: Prog node + defer coff.symbol_table.pending_shrink = false; + const sub_prog_node = coff.idleProgNode( + tid, + coff.symbol_prog_node, + coff.getNode(coff.symbol_table.ni), + ); + defer sub_prog_node.end(); + const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); - Node.known.symbol_table.shrink( + coff.symbol_table.ni.shrink( &coff.mf, comp.gpa, number_of_symbols * std.coff.Symbol.sizeOf(), @@ -3119,7 +3190,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return comp.link_diags.fail( - "linker failed to shrink symbol table: {t}", + "linker failed to compact symbol table: {t}", .{e}, ), }; @@ -3129,6 +3200,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } if (coff.pending_uavs.count() > 0) return true; if (coff.globals.count() > coff.global_pending_index) return true; + if (coff.symbol_table.pending.count() > 0) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; if (coff.mf.updates.items.len > 0) return true; if (coff.pending_members.count() > 0) return true; @@ -3159,6 +3231,7 @@ fn idleProgNode( .tid = tid, }), }) catch &name, + .archive_member => |mi| &mi.get(coff).headerPtr(coff).name, }, 0); } @@ -3182,6 +3255,7 @@ fn flushUav( .{ .read = true }, )).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); const sym = si.get(coff); const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ .alignment = uav_align.toStdMem(), @@ -3200,8 +3274,8 @@ fn flushUav( const sym = si.get(coff); assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - if (sym.target_relocs != .none) - _ = try coff.updateSymbolTableEntry(si); + if (!isImage(coff) and sym.target_relocs != .none) + try coff.pendingSymbolTableEntry(si); break :ni sym.ni; }; @@ -3232,7 +3306,8 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { if (!coff.isImage()) { const si = gmi.symbol(coff); - assert(try coff.updateSymbolTableEntry(si) != .none); + try coff.pendingSymbolTableEntry(si); + if (si.get(coff).ni != .none) try coff.ensureMemberSymbol( gn.name, @@ -3429,6 +3504,9 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { } assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + if (!isImage(coff) and sym.target_relocs != .none) + try coff.pendingSymbolTableEntry(si); + break :ni sym.ni; }; @@ -3464,15 +3542,20 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .data_directories, .section_table, .placeholder, - .symbol_table_entry, // TODO: Need to impl this for symbol table updates to work? - .string_table, - => if (!coff.isArchive()) unreachable, + => if (coff.isImage()) unreachable, .symbol_table => { coff.targetStore( &coff.headerPtr().pointer_to_symbol_table, @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]), ); }, + .string_table => { + if (!coff.symbol_table.pending_shrink) { + const symbol_table_loc, const symbol_table_size = coff.symbol_table.ni.location(&coff.mf).resolve(&coff.mf); + const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf); + coff.symbol_table.pending_shrink = string_table_offset - (symbol_table_loc + symbol_table_size) > 0; + } + }, .relocation_table => |sn| { coff.targetStore( &sn.header(coff).pointer_to_relocations, @@ -3621,7 +3704,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { } fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { - _, const size = ni.location(&coff.mf).resolve(&coff.mf); + const offset, const size = ni.location(&coff.mf).resolve(&coff.mf); log.debug("flushResized({s}, 0x{x})", .{ @tagName(coff.getNode(ni)), size }); switch (coff.getNode(ni)) { @@ -3657,7 +3740,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { .archive_member => |mi| { const content_ni = mi.get(coff).content_ni; const next_ni = content_ni.next(&coff.mf); - const offset, _ = content_ni.location(&coff.mf).resolve(&coff.mf); + const content_offset, _ = content_ni.location(&coff.mf).resolve(&coff.mf); const next_offset = switch (next_ni) { .none => offset: { assert(content_ni.parent(&coff.mf) == Node.known.file); @@ -3672,15 +3755,22 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { }; // Not inserting IMAGE_ARCHIVE_PAD `\n` byte here, because we are expanding to full size - Member.storeHeaderDecimalStr(&mi.get(coff).headerPtr(coff).size, next_offset - offset); + Member.storeHeaderDecimalStr(&mi.get(coff).headerPtr(coff).size, next_offset - content_offset); }, .coff_header, .optional_header, .data_directories, => unreachable, .section_table => {}, - .symbol_table => assert(!coff.isImage()), - .symbol_table_entry => unreachable, + .symbol_table => { + assert(!coff.isImage()); + if (!coff.symbol_table.pending_shrink) { + const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf); + coff.symbol_table.pending_shrink = + size > coff.targetLoad(&coff.headerPtr().number_of_symbols) * std.coff.Symbol.sizeOf() or + string_table_offset - (offset + size) > 0; + } + }, .string_table => { assert(!coff.isImage()); coff.targetStore(coff.symbolTableStringLenPtr(), @intCast(size)); @@ -3904,17 +3994,18 @@ fn updateExportsInner( export_sym.rva = exported_sym.rva; export_sym.size = exported_sym.size; export_sym.section_number = exported_sym.section_number; - export_si.applyTargetRelocs(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)) { - const tls_directory = coff.dataDirectoryPtr(.TLS); - tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.size }; - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); - } + defer export_si.applyTargetRelocs(coff); - if (coff.export_table.ni == .none) continue; + 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)) { + const tls_directory = coff.dataDirectoryPtr(.TLS); + tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.size }; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); + } + } else continue; const entries_ctx = ExportTable.Adapter{ .coff = coff }; const gop = try coff.export_table.entries.getOrPutAdapted( @@ -4002,7 +4093,6 @@ fn updateExportsInner( gop.value_ptr.si = export_si; const reloc = gop.value_ptr.*.export_address_table_ri.get(coff); reloc.target = export_si; - export_si.applyTargetRelocs(coff); // TODO: Potentially doing this twice, defer first one? } } } -- 2.54.0 From 84142ad56bb22105f23a357201973a91a90e21b9 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 11/94] Coff: support build-obj - Fix MappedFile.nodeResize causing USER_MAPPED_FILE when resizing the root node when the requested size is smaller than the previous resize + growth factor. --- src/link/Coff.zig | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 1d69d5f9d7726164487fe15ee098fc8c0cb29389..02c133b9858b9a094be61ecd1efea2379ff2643d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -149,7 +149,6 @@ pub const Node = union(enum) { header, /// Images and archives only. signature, - /// Archives only. archive_member_header: Member.Index, archive_member: Member.Index, @@ -159,6 +158,7 @@ pub const Node = union(enum) { optional_header, /// Image only data_directories, + section_table, // Archives and objects only symbol_table, @@ -1201,6 +1201,15 @@ fn isArchive(coff: *const Coff) bool { }; } +fn isObj(coff: *const Coff) bool { + return coff.base.comp.config.output_mode == .Obj; +} + +fn zcuSectionParent(coff: *Coff) MappedFile.Node.Index { + assert(coff.base.comp.zcu != null); + return if (coff.isArchive()) Node.known.zcu_member else Node.known.file; +} + fn initHeaders( coff: *Coff, machine: std.coff.IMAGE.FILE.MACHINE, @@ -1261,7 +1270,7 @@ fn initHeaders( const archive_signature = "!\n"; const signature_ni = Node.known.signature; - assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image) header_ni else Node.known.file, .{ + assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{ .size = if (is_image) msdos_stub.len + pe_signature.len else if (is_archive) @@ -2448,12 +2457,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags @sizeOf(std.coff.SectionHeader) * section_table_len, ); - const parent_ni = if (coff.isArchive()) - Node.known.zcu_member - else - Node.known.file; - - const ni = try coff.mf.addLastChildNode(gpa, parent_ni, .{ + const ni = try coff.mf.addLastChildNode(gpa, coff.zcuSectionParent(), .{ .alignment = coff.mf.flags.block_size, .moved = true, .bubbles_moved = false, @@ -2652,7 +2656,7 @@ pub fn addReloc( const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); if (section.relocation_table_ni == .none) { try coff.nodes.ensureUnusedCapacity(gpa, 1); - section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, Node.known.zcu_member, .{ + section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, coff.zcuSectionParent(), .{ .size = new_size, .alignment = .@"2", .moved = true, @@ -3308,7 +3312,7 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const si = gmi.symbol(coff); try coff.pendingSymbolTableEntry(si); - if (si.get(coff).ni != .none) + if (coff.isArchive() and si.get(coff).ni != .none) try coff.ensureMemberSymbol( gn.name, coff.getNode(Node.known.zcu_member).archive_member, @@ -3542,7 +3546,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .data_directories, .section_table, .placeholder, - => if (coff.isImage()) unreachable, + => assert(!coff.isImage()), .symbol_table => { coff.targetStore( &coff.headerPtr().pointer_to_symbol_table, -- 2.54.0 From 3c3f5dfc4fc87fabe8b9a64a7589d9af09f64d7f Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 12/94] Coff: Load object inputs - Create globals for all symbols (images) - Copy the object into a new member and index it's symbols (archives) - Support build-lib with no zcu (ie. a c file or obj inputs) --- src/link/Coff.zig | 292 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 254 insertions(+), 38 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 02c133b9858b9a094be61ecd1efea2379ff2643d..56dda448311a998181555facf598240b2f2fb87a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -334,7 +334,10 @@ pub const Member = struct { kind: Kind, header_ni: MappedFile.Node.Index, content_ni: MappedFile.Node.Index, - first_linker_indices: std.AutoArrayHashMapUnmanaged(Symbol.Index, FirstLinkerIndex), + first_linker_indices: std.AutoArrayHashMapUnmanaged(struct { + mi: Member.Index, + name: String, + }, FirstLinkerIndex), pub const Kind = enum { first_linker, @@ -670,7 +673,7 @@ pub const Symbol = struct { section_number: SectionNumber, sti: SymbolTable.Index, gmi: Node.GlobalMapIndex, - unused1: u16 = 0, + unused0: u16 = 0, pub const SectionNumber = enum(i16) { UNDEFINED = 0, @@ -1319,8 +1322,11 @@ fn initHeaders( break :parent zcu_member.content_ni; } - assert(Node.known.zcu_member_header == try coff.mf.addLastChildNode(gpa, Node.known.file, .{})); - assert(Node.known.zcu_member == try coff.mf.addLastChildNode(gpa, Node.known.file, .{})); + // These placeholder nodes are placed before the first member - if there are + // no other members then the last linker member (longnames) needs to expand + // to fill the padding at the end of the file. + assert(Node.known.zcu_member_header == try coff.mf.addNodeAfter(gpa, Node.known.header, .{})); + assert(Node.known.zcu_member == try coff.mf.addNodeAfter(gpa, Node.known.header, .{})); coff.nodes.appendAssumeCapacity(.placeholder); coff.nodes.appendAssumeCapacity(.placeholder); @@ -1339,7 +1345,7 @@ fn initHeaders( const zcu_coff_parent_ni = opt_zcu_coff_parent_ni orelse { // If we're not generating any code, no more known nodes are used while (coff.nodes.len < Node.known_count) { - _ = try coff.mf.addLastChildNode(gpa, Node.known.file, .{}); + _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{}); coff.nodes.appendAssumeCapacity(.placeholder); } @@ -1976,11 +1982,20 @@ fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional { return (try coff.getOrPutString(string orelse return .none)).toOptional(); } +/// `len` does not include null terminators fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void { const gpa = coff.base.comp.gpa; try coff.strings.ensureUnusedCapacityContext(gpa, 1, .{ .bytes = &coff.string_bytes }); try coff.string_bytes.ensureUnusedCapacity(gpa, len + 1); } + +/// `total_len` includes null terminators +fn ensureManyUnusedStringCapacity(coff: *Coff, num_strings: u32, total_len: usize) !void { + const gpa = coff.base.comp.gpa; + try coff.strings.ensureUnusedCapacityContext(gpa, num_strings, .{ .bytes = &coff.string_bytes }); + try coff.string_bytes.ensureUnusedCapacity(gpa, total_len + num_strings); +} + fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { const gop = coff.strings.getOrPutAssumeCapacityAdapted( string, @@ -1995,10 +2010,15 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { return @enumFromInt(gop.key_ptr.*); } -pub fn globalSymbol(coff: *Coff, opts: struct { +const GlobalOptions = struct { name: []const u8, lib_name: ?[]const u8 = null, -}) !Symbol.Index { +}; + +fn getOrPutGlobalSymbol( + coff: *Coff, + opts: GlobalOptions, +) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult { const gpa = coff.base.comp.gpa; try coff.symbols.ensureUnusedCapacity(gpa, 1); const sym_gop = try coff.globals.getOrPut(gpa, .{ @@ -2012,7 +2032,11 @@ pub fn globalSymbol(coff: *Coff, opts: struct { coff.synth_prog_node.increaseEstimatedTotalItems(1); } - return sym_gop.value_ptr.*; + return sym_gop; +} + +pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { + return (try coff.getOrPutGlobalSymbol(opts)).value_ptr.*; } pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void { @@ -2225,22 +2249,14 @@ fn appendMemberSymbolString( name_slice[name.len] = 0; } -fn ensureMemberSymbol( - coff: *Coff, - name: String, - mi: Member.Index, - si: Symbol.Index, -) !void { +fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void { const gpa = coff.base.comp.gpa; const member = mi.get(coff); assert(member.kind == .coff); - const gop = try member.first_linker_indices.getOrPut(gpa, si); + const gop = try member.first_linker_indices.getOrPut(gpa, .{ .mi = mi, .name = name }); if (gop.found_existing) return; - // TODO: Detect duplicate names (ie. a name used by a symbol in another member, - // not the zcu since those already go through globals) - const mfli: Member.FirstLinkerIndex = blk: { const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr(); const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big); @@ -2276,14 +2292,15 @@ fn ensureMemberSymbol( const new_header_size = old_header_size + @sizeOf(u16); try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size); - const needs_sort = if (coff.lib_string_table.items.len > 0) + const old_needs_sort = coff.pending_members.get(Member.Index.second) != null; + const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0) std.mem.lessThan( u8, name_slice, coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff), ) else - false; + false); try coff.lib_string_table.append(gpa, name); @@ -2291,13 +2308,13 @@ fn ensureMemberSymbol( const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..])); coff.targetStore(num_symbols_ptr, @intFromEnum(mfli) + 1); - if (needs_sort) { - // The entire string table is rebuilt in flushMember after sorting - coff.pending_members.putAssumeCapacity(Member.Index.second, {}); - } else { + if (!needs_sort) { @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]); slice[new_header_size + coff.lib_string_len + name_slice.len] = 0; + } else if (!old_needs_sort) { + // The entire string table is rebuilt in flushMember after sorting + coff.pending_members.putAssumeCapacity(Member.Index.second, {}); } // Indices in this table are 1-based @@ -2708,16 +2725,216 @@ pub fn addReloc( target.target_relocs = ri; } -pub fn loadInput(coff: *Coff, input: link.Input) void { - _ = coff; +pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError || + Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void { + const io = coff.base.comp.io; + var buf: [4096]u8 = undefined; switch (input) { + .object => |object| { + var fr = object.file.reader(io, &buf); + coff.loadObject(object.path, null, &fr, .{ + .offset = fr.logicalPos(), + .size = try fr.getSize(), + }) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, + else => |e| return e, + }; + }, + .archive => |archive| { + var fr = archive.file.reader(io, &buf); + coff.loadArchive(archive.path, &fr) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, + else => |e| return e, + }; + }, + .res => |res| { + var fr = res.file.reader(io, &buf); + coff.loadRes(res.path, &fr) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, + else => |e| return e, + }; + }, + .dso => |dso| { + var fr = dso.file.reader(io, &buf); + coff.loadDll(dso.path, &fr) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, + else => |e| return e, + }; + }, .dso_exact => unreachable, - inline else => |i, tag| { - log.debug("loadInput({s}: {f})", .{ @tagName(tag), i.path.fmtEscapeString() }); - }, } } +fn fmtArchiveNameString(archiveName: ?[]const u8) std.fmt.Alt(?[]const u8, archiveNameStringEscape) { + return .{ .data = archiveName }; +} +fn archiveNameStringEscape(archiveName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { + try w.print("({f})", .{std.zig.fmtString(archiveName orelse return)}); +} + +fn loadObject( + coff: *Coff, + path: std.Build.Cache.Path, + archive_name: ?[]const u8, + fr: *Io.File.Reader, + fl: MappedFile.Node.FileLocation, +) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const r = &fr.interface; + const target = &comp.root_mod.resolved_target.result; + const target_endian = coff.targetEndian(); + const is_archive = coff.isArchive(); + + log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtArchiveNameString(archive_name) }); + const header = try r.peekStruct(std.coff.Header, coff.targetEndian()); + if (header.machine != target.toCoffMachine()) + return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{ + target.toCoffMachine(), + header.machine, + }); + if (header.number_of_sections == 0) return; + if (@sizeOf(std.coff.Header) + header.number_of_sections * @sizeOf(std.coff.SectionHeader) > fl.size) + return diags.failParse(path, "invalid section table", .{}); + const unexpected_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{ + .RELOCS_STRIPPED, + .EXECUTABLE_IMAGE, + .AGGRESSIVE_WS_TRIM, + .RESERVED, + .BYTES_REVERSED_LO, + .DLL, + .BYTES_REVERSED_HI, + }; + inline for (unexpected_flags) |flag| + if (@field(header.flags, @tagName(flag))) + return diags.failParse(path, "unexpected flag set: {t}", .{flag}); + + if (header.size_of_optional_header != 0) + return diags.failParse(path, "unexpected optional header", .{}); + + const symbol_table_len = header.number_of_symbols * std.coff.Symbol.sizeOf(); + const symbol_table_end = header.pointer_to_symbol_table + symbol_table_len; + // String table length (which includes the length field) immediately trails the symbol table + if (symbol_table_end + @sizeOf(u32) > fl.size) + return diags.failParse(path, "bad symbol table location", .{}); + + try fr.seekTo(fl.offset + symbol_table_end); + const string_table_len = try r.peekInt(u32, target_endian); + if (string_table_len < @sizeOf(u32) or + symbol_table_end + string_table_len > fl.size) + return diags.failParse(path, "bad string table", .{}); + + const string_table = string_table: { + const string_table = try gpa.alloc(u8, string_table_len); + errdefer gpa.free(string_table); + try r.readSliceAll(string_table); + break :string_table string_table; + }; + defer gpa.free(string_table); + + try coff.ensureManyUnusedStringCapacity( + header.number_of_symbols, + string_table_len - @sizeOf(u32), + ); + + const mi = if (is_archive) mi: { + try coff.nodes.ensureUnusedCapacity(gpa, 2); + try coff.members.ensureUnusedCapacity(gpa, 1); + + const mi = try coff.addMemberAssumeCapacity(.coff, fl.size); + const member = mi.get(coff); + try member.initHeader(coff, path.sub_path, header.time_date_stamp); + + { + var nw: MappedFile.Node.Writer = undefined; + member.content_ni.writer(&coff.mf, gpa, &nw); + defer nw.deinit(); + + try fr.seekTo(fl.offset); + try r.streamExact(&nw.interface, fl.size); + } + + break :mi mi; + } else undefined; + + try fr.seekTo(fl.offset + header.pointer_to_symbol_table); + const symbol_size = std.coff.Symbol.sizeOf(); + + var symbol_ix: u32 = 0; + while (symbol_ix < header.number_of_symbols) { + const symbol: *align(2) std.coff.Symbol = @ptrCast(@alignCast(try r.take(symbol_size))); + defer { + r.toss(symbol.number_of_aux_symbols * symbol_size); + symbol_ix += symbol.number_of_aux_symbols + 1; + } + + switch (symbol.section_number) { + .UNDEFINED, .ABSOLUTE, .DEBUG => continue, + else => switch (symbol.storage_class) { + .STATIC => if (symbol.value == 0) continue, + .EXTERNAL => {}, + else => continue, + }, + } + + const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { + const index = std.mem.readInt(u32, symbol.name[4..], target_endian); + if (index >= string_table.len) + return diags.failParse(path, "bad string offset for symbol {d}", .{symbol_ix}); + break :name string_table[index..]; + } else &symbol.name, 0); + + if (is_archive) { + try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); + continue; + } + + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); + if (global_gop.found_existing) + return diags.failParse(path, "multiple definitions of '{s}'", .{name}); + } +} + +fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const r = &fr.interface; + + log.debug("loadArchive({f})", .{path.fmtEscapeString()}); + + _ = gpa; + _ = diags; + _ = r; +} + +fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const r = &fr.interface; + + log.debug("loadRes({f})", .{path.fmtEscapeString()}); + + _ = gpa; + _ = diags; + _ = r; +} + +fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const r = &fr.interface; + + log.debug("loadDll({f})", .{path.fmtEscapeString()}); + + _ = gpa; + _ = diags; + _ = r; +} + pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { _ = coff; _ = prog_node; @@ -3074,7 +3291,6 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } if (coff.global_pending_index < coff.globals.count()) { - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid }; const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index); coff.global_pending_index += 1; const sub_prog_node = coff.synth_prog_node.start( @@ -3082,7 +3298,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { 0, ); defer sub_prog_node.end(); - coff.flushGlobal(pt, gmi) catch |err| switch (err) { + coff.flushGlobal(gmi) catch |err| switch (err) { else => |e| return e, error.MappedFileIo => return comp.link_diags.fail( "linker failed to lower constant: {t}", @@ -3301,22 +3517,19 @@ fn flushUav( si.applyLocationRelocs(coff); } -fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { - const zcu = pt.zcu; - const comp = zcu.comp; - const gpa = zcu.gpa; +fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; const gn = gmi.globalName(coff); log.debug("flushGlobal({s}, {?s}) = {d}", .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), gmi.symbol(coff) }); if (!coff.isImage()) { const si = gmi.symbol(coff); try coff.pendingSymbolTableEntry(si); - if (coff.isArchive() and si.get(coff).ni != .none) try coff.ensureMemberSymbol( - gn.name, coff.getNode(Node.known.zcu_member).archive_member, - si, + gn.name, ); return; @@ -3715,6 +3928,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { .file => { if (coff.isArchive() and coff.members.items.len > 0) { const last_member = coff.members.items[coff.members.items.len - 1]; + // See .archive_member branch for reasoning assert(Node.known.file.reverseChildren(&coff.mf).ni == last_member.content_ni); try coff.flushResized(last_member.content_ni); } @@ -3867,6 +4081,8 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void { } }; + // TODO: Does this sort need to also sort by linker input order (if names equal)? + std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{ .coff = coff, .indices = coff.secondLinkerMemberIndicesSlice(), -- 2.54.0 From 9bf95b438196f8eef28a37e35976142aea152ae9 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 13/94] Coff: Progress on loading inputs - Fixup writing to the longnames member - More .obj parsing --- src/link/Coff.zig | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 56dda448311a998181555facf598240b2f2fb87a..bf650cd9bee670b64e2053c16affd8ecbd63c1cb 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -368,35 +368,11 @@ pub const Member = struct { return @ptrCast(@alignCast(member.header_ni.slice(&coff.mf))); } - pub fn initHeader(member: *Member, coff: *Coff, name: []const u8, timestamp: u32) !void { - const header = member.headerPtr(coff); - try storeHeaderName(coff, &header.name, name); - storeHeaderDecimalStr(&header.date, timestamp); - - // Matching the Microsoft behaviour of emitting blanks for these fields - header.user_id = @splat(' '); - header.group_id = @splat(' '); - - // file_mode is actually octal, but we only ever write 0 to it - storeHeaderDecimalStr(&header.file_mode, 0); - if (!member.content_ni.hasResized(&coff.mf)) - storeHeaderDecimalStr( - &header.size, - member.content_ni.location(&coff.mf).resolve(&coff.mf)[1], - ); - - @memcpy(&header.end_of_header, "`\n"); - } - /// Sets `name` as the name field of this member's header, either directly (if it's short enough), /// or by creating an entry in the longnames member and storing a reference to that entry. - pub fn storeHeaderName(coff: *Coff, field: *[16]u8, name: []const u8) !void { - if (name.len < field.len) { - @memcpy(field[0..name.len], name); - field[name.len] = '/'; - const padding = field.len - name.len - 1; - if (padding > 0) @memset(field[field.len - padding ..], ' '); - } else { + pub fn initHeader(member: *Member, coff: *Coff, name: []const u8, timestamp: u32) !void { + const max_name_len = @typeInfo(@FieldType(std.coff.ArchiveMemberHeader, "name")).array.len; + const opt_name_offset = if (name.len >= max_name_len) offset: { const gpa = coff.base.comp.gpa; const entries_ctx = LongNamesTable.Adapter{ .coff = coff }; const gop = try coff.long_names_table.entries.getOrPutAdapted( @@ -410,7 +386,7 @@ pub const Member = struct { _, const old_size = Node.known.longnames_member.location(&coff.mf).resolve(&coff.mf); const new_size = old_size + name.len + 1; - assert(new_size < comptime try std.math.powi(u64, 10, field.len - 1)); + assert(new_size < comptime try std.math.powi(u64, 10, max_name_len - 1)); try Node.known.longnames_member.resize(&coff.mf, gpa, new_size); const name_table_slice = Node.known.longnames_member.slice(&coff.mf); -- 2.54.0 From 14a7131c4f97895d6ab600b88940b6afb6d6ae4e Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 14/94] Coff: More input loading progress - Map sections by name, so we can create them on-demand when linking non-images - Verify that parent section attributes match when adding a pseudo / objection section - Support section names with len > 8 in non-images --- src/link/Coff.zig | 346 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 278 insertions(+), 68 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bf650cd9bee670b64e2053c16affd8ecbd63c1cb..3e4a07fab36bf453280d239ab299e529ba93f172 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,7 +38,7 @@ strings: std.HashMapUnmanaged( std.hash_map.default_max_load_percentage, ), string_bytes: std.ArrayList(u8), -section_table: std.ArrayList(Section), +section_table: std.AutoArrayHashMapUnmanaged(String, Section), tls_si: Symbol.Index, pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), @@ -395,14 +395,40 @@ pub const Member = struct { name_slice[name.len] = 0; gop.value_ptr.* = .{ - .index = old_size, + .offset = old_size, .len = name.len, }; } - field[0] = '/'; - storeHeaderDecimalStr(field[1..], gop.value_ptr.index); + break :offset gop.value_ptr.offset; + } else null; + + const header = member.headerPtr(coff); + if (opt_name_offset) |name_offset| { + header.name[0] = '/'; + storeHeaderDecimalStr(header.name[1..], name_offset); + } else { + @memcpy(header.name[0..name.len], name); + header.name[name.len] = '/'; + const padding = max_name_len - name.len - 1; + @memset(header.name[max_name_len - padding ..], ' '); } + + storeHeaderDecimalStr(&header.date, timestamp); + + // Matching the Microsoft behaviour of emitting blanks for these fields + header.user_id = @splat(' '); + header.group_id = @splat(' '); + + // file_mode is actually octal, but we only ever write 0 to it + storeHeaderDecimalStr(&header.file_mode, 0); + if (!member.content_ni.hasResized(&coff.mf)) + storeHeaderDecimalStr( + &header.size, + member.content_ni.location(&coff.mf).resolve(&coff.mf)[1], + ); + + @memcpy(&header.end_of_header, "`\n"); } pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void { @@ -422,7 +448,7 @@ pub const LongNamesTable = struct { entries: std.AutoArrayHashMapUnmanaged(void, Entry), pub const Entry = struct { - index: u64, + offset: u64, len: u64, }; @@ -433,7 +459,7 @@ pub const LongNamesTable = struct { assert(adapter.coff.isArchive()); // TODO: move to helper that uses this const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf); const rhs = adapter.coff.long_names_table.entries.values()[rhs_index]; - return std.mem.eql(u8, longnames_slice[rhs.index..][0..rhs.len], lhs_key); + return std.mem.eql(u8, longnames_slice[rhs.offset..][0..rhs.len], lhs_key); } pub fn hash(_: Adapter, key: []const u8) u32 { @@ -463,6 +489,19 @@ pub const SymbolTable = struct { pub const SymbolName = union(enum) { short: []const u8, long: StringIndex, + + pub fn store(name: SymbolName, coff: *const Coff, field: *[8]u8) void { + switch (name) { + .short => |s| { + @memcpy(field[0..s.len], s); + @memset(field[s.len..], 0); + }, + .long => |l| { + @memset(field[0..4], 0); + std.mem.writePackedInt(u32, field[4..], 0, @intFromEnum(l), coff.targetEndian()); + }, + } + } }; // Symbol.Index does not map 1:1 with SymbolTable.Index: @@ -666,7 +705,7 @@ pub const Symbol = struct { } pub fn section(sn: SectionNumber, coff: *const Coff) *Section { - return &coff.section_table.items[sn.toIndex()]; + return &coff.section_table.values()[sn.toIndex()]; } pub fn header(sn: SectionNumber, coff: *Coff) *std.coff.SectionHeader { @@ -693,6 +732,13 @@ pub const Symbol = struct { return ni; } + pub fn knownString(si: Symbol.Index) String.Optional { + return switch (si) { + .null, _ => .none, + inline else => |tag| @field(String.Optional, "." ++ @tagName(tag)), + }; + } + pub fn flushMoved(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); sym.rva = coff.computeNodeRva(sym.ni); @@ -1522,16 +1568,16 @@ fn initHeaders( .sti = .none, .gmi = .none, }; - assert(try coff.addSection(".data", .{ + assert(try coff.addSection(.@".data", .{ .CNT_INITIALIZED_DATA = true, .MEM_READ = true, .MEM_WRITE = true, }) == .data); - assert(try coff.addSection(".rdata", .{ + assert(try coff.addSection(.@".rdata", .{ .CNT_INITIALIZED_DATA = true, .MEM_READ = true, }) == .rdata); - assert(try coff.addSection(".text", .{ + assert(try coff.addSection(.@".text", .{ .CNT_CODE = true, .MEM_EXECUTE = true, .MEM_READ = true, @@ -1625,7 +1671,7 @@ fn initHeaders( if (comp.config.any_non_single_threaded) { if (!is_image) - coff.tls_si = try coff.addSection(".tls$", .{ + coff.tls_si = try coff.addSection(.@".tls$", .{ .CNT_INITIALIZED_DATA = true, .MEM_READ = true, .MEM_WRITE = true, @@ -1637,7 +1683,7 @@ fn initHeaders( _ = try coff.objectSectionMapIndex( .@".tls$", coff.mf.flags.block_size, - .{ .read = true, .write = !is_image, .tls = true }, + .{ .read = true, .write = !is_image }, ); } } @@ -1877,7 +1923,7 @@ pub fn dataDirectoryPtr( pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader { return @ptrCast(@alignCast( - Node.known.section_table.slice(&coff.mf)[0 .. coff.section_table.items.len * @sizeOf(std.coff.SectionHeader)], + Node.known.section_table.slice(&coff.mf)[0 .. coff.section_table.count() * @sizeOf(std.coff.SectionHeader)], )); } @@ -1958,6 +2004,30 @@ fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional { return (try coff.getOrPutString(string orelse return .none)).toOptional(); } +/// If the name does not fit in the symbol header, adds it to the symbol table string table. +/// If the caller knows this name already has a String associated with it, they can avoid +/// a redundant call to `getOrPutString` by specifying `opt_string`. +/// The lifetime of the return value matches that of `name`. +fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !SymbolTable.SymbolName { + assert(!coff.isImage()); + const gpa = coff.base.comp.gpa; + return if (name.len > 8) name: { + const string = opt_string orelse try coff.getOrPutString(name); + const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string); + if (!string_gop.found_existing) { + const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1]; + string_gop.value_ptr.* = @enumFromInt(string_index); + + try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name.len + 1); + const slice = coff.symbol_table.strings_ni.slice(&coff.mf); + @memcpy(slice[string_index..][0..name.len], name); + slice[string_index + name.len] = 0; + } + + break :name .{ .long = string_gop.value_ptr.* }; + } else .{ .short = name }; +} + /// `len` does not include null terminators fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void { const gpa = coff.base.comp.gpa; @@ -2035,7 +2105,7 @@ fn navSection( const ip = &zcu.intern_pool; const default: String, const attributes: ObjectSectionAttributes = if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{ - .@".tls$", .{ .read = true, .write = true, .tls = true }, + .@".tls$", .{ .read = true, .write = !coff.isImage() }, } else if (ip.isFunctionType(nav_resolved.type)) .{ .@".text", .{ .read = true, .execute = true }, } else if (nav_resolved.@"const") .{ @@ -2311,12 +2381,11 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void const entry = coff.symbolTableEntryPtr(sym.sti) orelse entry: { var buf: [15]u8 = undefined; - const name_slice, const opt_name_string, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = + const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { const gn = sym.gmi.globalName(coff); break :blk .{ - gn.name.toSlice(coff), - gn.name, + try coff.getOrPutSymbolName(gn.name.toSlice(coff), gn.name), 0, if (Symbol.Index.text.get(coff).section_number == sym.section_number) .FUNCTION @@ -2325,8 +2394,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }; } else blk: switch (coff.getNode(sym.ni)) { .image_section => .{ - &sym.section_number.header(coff).name, - null, + try coff.getOrPutSymbolName(&sym.section_number.header(coff).name, null), 1, .NULL, }, @@ -2335,8 +2403,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void const ip = &zcu.intern_pool; const nav = ip.getNav(nmi.navIndex(coff)); break :blk .{ - nav.fqn.toSlice(ip), - null, + try coff.getOrPutSymbolName(nav.fqn.toSlice(ip), null), 0, if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL, }; @@ -2344,7 +2411,11 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void .uav => |umi| { var w = Io.Writer.fixed(&buf); w.print("__anon_{x}", .{umi.uavValue(coff)}) catch unreachable; - break :blk .{ w.buffered(), null, 0, .NULL }; + break :blk .{ + try coff.getOrPutSymbolName(w.buffered(), null), + 0, + .NULL, + }; }, inline .lazy_code, .lazy_const_data => |mi, tag| { const lazy_sym = mi.lazySymbol(coff); @@ -2355,7 +2426,11 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void defer gpa.free(name); const string = try coff.getOrPutString(name); - break :blk .{ string.toSlice(coff), string, 0, if (tag == .lazy_code) .FUNCTION else .NULL }; + break :blk .{ + try coff.getOrPutSymbolName(string.toSlice(coff), string), + 0, + if (tag == .lazy_code) .FUNCTION else .NULL, + }; }, else => { log.err("TODO implement symbol table init for {s} ({d})", .{ @tagName(coff.getNode(sym.ni)), si }); @@ -2363,22 +2438,6 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }, }; - const symbol_name: SymbolTable.SymbolName = if (name_slice.len > 8) name: { - const string = opt_name_string orelse try coff.getOrPutString(name_slice); - const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string); - if (!string_gop.found_existing) { - const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1]; - string_gop.value_ptr.* = @enumFromInt(string_index); - - try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name_slice.len + 1); - const slice = coff.symbol_table.strings_ni.slice(&coff.mf); - @memcpy(slice[string_index..][0..name_slice.len], name_slice); - slice[string_index + name_slice.len] = 0; - } - - break :name .{ .long = string_gop.value_ptr.* }; - } else .{ .short = name_slice }; - const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); const new_num_symbols = old_num_symbols + 1 + num_aux_symbols; @@ -2389,17 +2448,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void si.flushSymbolTableIndex(coff); const entry = coff.symbolTableEntryPtr(sym.sti).?; - switch (symbol_name) { - .short => |s| { - @memcpy(entry.name[0..s.len], s); - @memset(entry.name[s.len..], 0); - }, - .long => |l| { - @memset(entry.name[0..4], 0); - const offset_ptr: *align(2) u32 = @ptrCast(entry.name[4..]); - coff.targetStore(offset_ptr, @intFromEnum(l)); - }, - } + symbol_name.store(coff, &entry.name); entry.section_number = @enumFromInt(@intFromEnum(sym.section_number)); entry.type = .{ @@ -2431,7 +2480,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void log.debug("updateSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); } -fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index { +fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { assert(coff.base.comp.zcu != null); const gpa = coff.base.comp.gpa; @@ -2457,7 +2506,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags }); const si = coff.addSymbolAssumeCapacity(); - coff.section_table.appendAssumeCapacity(.{ + coff.section_table.putAssumeCapacity(name, .{ .si = si, .relocation_table_ni = .none, }); @@ -2468,7 +2517,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags const virtual_size = coff.optionalHeaderField(.section_alignment); const rva: u32 = switch (section_index) { 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]), - else => coff.section_table.items[section_index - 1].si.get(coff).rva + + else => coff.section_table.values()[section_index - 1].si.get(coff).rva + coff.targetLoad(§ion_table[section_index - 1].virtual_size), }; @@ -2494,12 +2543,13 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags .number_of_linenumbers = 0, .flags = flags, }; - @memcpy(section.name[0..name.len], name); - @memset(section.name[name.len..], 0); if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.SectionHeader, section); + const name_slice = name.toSlice(coff); if (coff.isImage()) { + @memcpy(section.name[0..name_slice.len], name_slice); + @memset(section.name[name_slice.len..], 0); switch (coff.optionalHeaderPtr()) { inline else => |optional_header| coff.targetStore( &optional_header.size_of_image, @@ -2507,6 +2557,7 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags ), } } else { + (try coff.getOrPutSymbolName(name_slice, name)).store(coff, §ion.name); try coff.pendingSymbolTableEntry(si); } @@ -2522,7 +2573,34 @@ const ObjectSectionAttributes = packed struct { nocache: bool = false, discard: bool = false, remove: bool = false, - tls: bool = false, + + // TODO: Include init / not init flags? + + pub fn fromFlags(flags: std.coff.SectionHeader.Flags) ObjectSectionAttributes { + return .{ + .read = flags.MEM_READ, + .write = flags.MEM_WRITE, + .execute = flags.MEM_EXECUTE, + .shared = flags.MEM_SHARED, + .nopage = flags.MEM_NOT_PAGED, + .nocache = flags.MEM_NOT_CACHED, + .discard = flags.MEM_DISCARDABLE, + .remove = flags.LNK_REMOVE, + }; + } + + pub fn asFlags(attr: ObjectSectionAttributes) std.coff.SectionHeader.Flags { + return .{ + .MEM_READ = attr.read, + .MEM_WRITE = attr.write, + .MEM_EXECUTE = attr.execute, + .MEM_SHARED = attr.shared, + .MEM_NOT_PAGED = attr.nopage, + .MEM_NOT_CACHED = attr.nocache, + .MEM_DISCARDABLE = attr.discard, + .LNK_REMOVE = attr.remove, + }; + } }; fn pseudoSectionMapIndex( @@ -2535,14 +2613,25 @@ fn pseudoSectionMapIndex( const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name); const psmi: Node.PseudoSectionMapIndex = @enumFromInt(pseudo_section_gop.index); if (!pseudo_section_gop.found_existing) { - const parent: Symbol.Index = if (attributes.execute) + const default_parent: Symbol.Index = if (attributes.execute) .text - else if (attributes.tls and coff.tls_si != .null) - coff.tls_si else if (attributes.write) .data else .rdata; + + const parent = if (coff.isImage() or std.mem.eql( + u8, + name.toSlice(coff), + default_parent.knownString().toSlice(coff).?, + )) + default_parent + else if (coff.section_table.get(name)) |section| parent: { + const header = section.si.get(coff).section_number.header(coff); + try coff.verifyParentSectionAttributes(name, name, .fromFlags(header.flags), attributes); + break :parent section.si; + } else try coff.addSection(name, attributes.asFlags()); + try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); const ni = try coff.mf.addLastChildNode(gpa, parent.node(coff), .{ .alignment = alignment }); @@ -2570,9 +2659,18 @@ fn objectSectionMapIndex( if (!object_section_gop.found_existing) { try coff.ensureUnusedStringCapacity(name.toSlice(coff).len); const name_slice = name.toSlice(coff); - const parent = (try coff.pseudoSectionMapIndex(coff.getOrPutStringAssumeCapacity( - name_slice[0 .. std.mem.indexOfScalar(u8, name_slice, '$') orelse name_slice.len], - ), alignment, attributes)).symbol(coff); + const prefix_index = std.mem.indexOfScalar(u8, name_slice, '$') orelse name_slice.len; + const parent_name = coff.getOrPutStringAssumeCapacity(if (coff.isImage()) + name_slice[0..prefix_index] + else + name_slice[0..@min(prefix_index + 1, name_slice.len)]); + const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, attributes)).symbol(coff); + try coff.verifyParentSectionAttributes( + parent_name, + name, + .fromFlags(parent.get(coff).section_number.header(coff).flags), + attributes, + ); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); const parent_ni = parent.node(coff); @@ -2610,6 +2708,33 @@ fn objectSectionMapIndex( return osmi; } +fn verifyParentSectionAttributes( + coff: *Coff, + parent_name: String, + child_name: String, + parent_attrs: ObjectSectionAttributes, + child_attrs: ObjectSectionAttributes, +) !void { + if (parent_attrs == child_attrs) return; + + const fields = std.meta.fields(ObjectSectionAttributes); + var err = try coff.base.comp.link_diags.addErrorWithNotes(fields.len); + try err.addMsg("object '{s}' was placed in parent section '{s}' with mismatched flags", .{ + child_name.toSlice(coff), + parent_name.toSlice(coff), + }); + + inline for (fields) |field| { + err.addNote("{s}: parent = {d} child = {d}", .{ + field.name, + @intFromBool(@field(child_attrs, field.name)), + @intFromBool(@field(parent_attrs, field.name)), + }); + } + + return error.LinkFailure; +} + pub fn addReloc( coff: *Coff, loc_si: Symbol.Index, @@ -2762,6 +2887,7 @@ fn loadObject( const target = &comp.root_mod.resolved_target.result; const target_endian = coff.targetEndian(); const is_archive = coff.isArchive(); + assert(!coff.isObj()); log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtArchiveNameString(archive_name) }); const header = try r.peekStruct(std.coff.Header, coff.targetEndian()); @@ -2773,7 +2899,7 @@ fn loadObject( if (header.number_of_sections == 0) return; if (@sizeOf(std.coff.Header) + header.number_of_sections * @sizeOf(std.coff.SectionHeader) > fl.size) return diags.failParse(path, "invalid section table", .{}); - const unexpected_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{ + const unexpected_header_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{ .RELOCS_STRIPPED, .EXECUTABLE_IMAGE, .AGGRESSIVE_WS_TRIM, @@ -2782,7 +2908,7 @@ fn loadObject( .DLL, .BYTES_REVERSED_HI, }; - inline for (unexpected_flags) |flag| + inline for (unexpected_header_flags) |flag| if (@field(header.flags, @tagName(flag))) return diags.failParse(path, "unexpected flag set: {t}", .{flag}); @@ -2810,17 +2936,88 @@ fn loadObject( defer gpa.free(string_table); try coff.ensureManyUnusedStringCapacity( - header.number_of_symbols, + header.number_of_sections + header.number_of_symbols, string_table_len - @sizeOf(u32), ); + const InputSection = struct { + header: std.coff.SectionHeader, + psmi: Node.PseudoSectionMapIndex, + }; + + try fr.seekTo(fl.offset + @sizeOf(std.coff.Header)); + const sections: []const InputSection = if (coff.isImage()) sections: { + const sections = try gpa.alloc(InputSection, header.number_of_sections); + errdefer gpa.free(sections); + + for (sections, 0..) |*section, section_i| { + section.header = try r.takeStruct(std.coff.SectionHeader, target_endian); + if (section.header.flags.LNK_INFO) { + if (std.mem.eql(u8, §ion.header.name, ".drectve")) + return diags.failParse(path, "TODO handle arguments in .drectve section", .{}); + + continue; + } + + if (section.header.flags.LNK_REMOVE or + section.header.flags.MEM_DISCARDABLE) + { + // TODO: Merge .debug$* sections and output to PDB + continue; + } + + if (section.header.flags.LNK_COMDAT) + // This will be necessary if we do the equivalent of /Gy for compiler-rt + return diags.failParse(path, "TODO handle COMDAT sections in input objects", .{}); + + const section_name_slice = if (section.header.name[0] == '/') name: { + const offset_str = std.mem.sliceTo(section.header.name[1..], 0); + const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch + return diags.failParse(path, "ill-formed section name in section {d}: '{s}'", .{ + section_i, + section.header.name[0 .. offset_str.len + 1], + }); + + if (name_offset > string_table.len) + return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset }); + + break :name std.mem.sliceTo(string_table[name_offset..], 0); + } else std.mem.sliceTo(§ion.header.name, 0); + + const section_name = coff.getOrPutStringAssumeCapacity(section_name_slice); + const osmi = try coff.objectSectionMapIndex( + section_name, + if (section.header.flags.ALIGN.toByteUnits()) |align_bytes| + .fromByteUnits(align_bytes) + else + .@"1", + .fromFlags(section.header.flags), + ); + + _ = osmi; + + // TODO: Decide to merge this section + // TODO: Map flags (might need to figure out a better tls flag?) + + //coff.objectSectionMapIndex(name: String, alignment: Alignment, attributes: ObjectSectionAttributes) + + // TODO: Load relocations, update for new offset? Or can just work with the object section parent? + + } + + break :sections sections; + } else &.{}; + defer gpa.free(sections); + const mi = if (is_archive) mi: { try coff.nodes.ensureUnusedCapacity(gpa, 2); try coff.members.ensureUnusedCapacity(gpa, 1); + const path_str = try path.toString(gpa); + defer gpa.free(path_str); const mi = try coff.addMemberAssumeCapacity(.coff, fl.size); const member = mi.get(coff); - try member.initHeader(coff, path.sub_path, header.time_date_stamp); + try member.initHeader(coff, path_str, header.time_date_stamp); { var nw: MappedFile.Node.Writer = undefined; @@ -2861,6 +3058,10 @@ fn loadObject( break :name string_table[index..]; } else &symbol.name, 0); + // Section numbers are 1-based here + if (!is_archive and @intFromEnum(symbol.section_number) > sections.len) + return diags.failParse(path, "bad section number {d} for '{s}'", .{ symbol.section_number, name }); + if (is_archive) { try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); continue; @@ -2869,6 +3070,9 @@ fn loadObject( const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); if (global_gop.found_existing) return diags.failParse(path, "multiple definitions of '{s}'", .{name}); + + // TODO: Get the sym and set the ni to point to wherever it was copied in the pseudo section + // TODO: May need to cache offsets and determine symbol sizes later (once we can sort by section offset) } } @@ -2880,6 +3084,12 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo log.debug("loadArchive({f})", .{path.fmtEscapeString()}); + // TODO: Skip over 1st linker member + // TODO: Build index of symbols -> members from 2nd linker member + // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images) + // TODO: Lazily call loadObject whenever a symbol is need from one of the members. + // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)? + _ = gpa; _ = diags; _ = r; @@ -3918,7 +4128,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ), } - if (size > coff.section_table.items[0].si.get(coff).rva) try coff.virtualSlide( + if (size > coff.section_table.values()[0].si.get(coff).rva) try coff.virtualSlide( 0, std.mem.alignForward( u32, @@ -4120,7 +4330,7 @@ fn flushExportsSort(coff: *Coff) void { fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void { var rva = start_rva; for ( - coff.section_table.items[start_section_index..], + coff.section_table.values()[start_section_index..], coff.sectionTableSlice()[start_section_index..], ) |*section, *header| { const section_sym = section.si.get(coff); -- 2.54.0 From b6192adfb22da64973c961297b60a74a5b6c9b23 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 15/94] Coff: Linking input objects - Input object validation - Load sections, symbols, and relocs from input objects - Load reloc addends from the reloc locations in input objects - Flush input sections into the output --- lib/std/coff.zig | 21 +- src/codegen/x86_64/Emit.zig | 6 +- src/link/Coff.zig | 781 ++++++++++++++++++++++++++++++------ 3 files changed, 669 insertions(+), 139 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 9ae5a4d67cebfea9cdfe3dc19dddec3e690f6487..b00d965d1c5636a699bb46dd507a0e7976a2dd6c 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -666,7 +666,7 @@ pub const Symbol = extern struct { storage_class: StorageClass, number_of_aux_symbols: u8, - pub fn sizeOf() usize { + pub fn sizeOf() comptime_int { return 18; } @@ -929,7 +929,7 @@ pub const WeakExternalDefinition = extern struct { unused: [10]u8, - pub fn sizeOf() usize { + pub fn sizeOf() comptime_int { return 18; } }; @@ -1393,7 +1393,7 @@ pub const Relocation = extern struct { symbol_table_index: u32, type: u16, - pub fn sizeOf() usize { + pub fn sizeOf() comptime_int { return 10; } }; @@ -1986,11 +1986,14 @@ pub const ArchiveMemberHeader = extern struct { end_of_header: [2]u8, }; -pub const FirstLinkerMemberHeader = extern struct { - /// Big-endian symbol count - number_of_symbols: u32, -}; +pub const LineNumber = extern struct { + type: extern union { + symbol_table_index: u32, + virtual_address: u32, + }, + line_number: u16, -pub const SecondLinkerMemberHeader = extern struct { - number_of_members: u32, + pub fn sizeOf() comptime_int { + return 6; + } }; diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index 2dd95342d3379307157fa9738ae0bcd82e8e2b9c..3d0d3c1829d72bc4a297a8817aedc550e00f7396 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -816,7 +816,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI @enumFromInt(@intFromEnum(emit.atom_id)), end_offset - 4, @enumFromInt(@intFromEnum(target.symbol)), - reloc.off, + .{ .known = reloc.off }, .{ .AMD64 = .REL32 }, ) else unreachable, .branch => |target| if (emit.bin_file.cast(.elf)) |elf_file| { @@ -854,7 +854,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI @enumFromInt(@intFromEnum(emit.atom_id)), end_offset - 4, @enumFromInt(@intFromEnum(target.symbol)), - reloc.off, + .{ .known = reloc.off }, .{ .AMD64 = .REL32 }, ) else return emit.fail("TODO implement {s} reloc for {s}", .{ @tagName(reloc.target), @tagName(emit.bin_file.tag), @@ -912,7 +912,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI @enumFromInt(@intFromEnum(emit.atom_id)), end_offset - 4, @enumFromInt(@intFromEnum(target.symbol)), - reloc.off, + .{ .known = reloc.off }, .{ .AMD64 = .SECREL }, ) else return emit.fail("TODO implement {s} reloc for {s}", .{ @tagName(reloc.target), @tagName(emit.bin_file.tag), diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 3e4a07fab36bf453280d239ab299e529ba93f172..6d519eb4b2bff5b07620243e5d6b57d05e5564d5 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -31,6 +31,18 @@ long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, symbol_table: SymbolTable, +inputs: std.ArrayList(struct { + path: std.Build.Cache.Path, + archive_name: ?[]const u8, + first_si: Symbol.Index, + last_si: Symbol.Index, +}), +input_sections: std.ArrayList(struct { + ii: Node.InputIndex, + si: Symbol.Index, + file_location: MappedFile.Node.FileLocation, +}), +input_section_pending_index: u32, strings: std.HashMapUnmanaged( u32, void, @@ -59,6 +71,7 @@ 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, dump_snapshot: bool, pub const default_file_alignment: u16 = 0x200; @@ -185,6 +198,7 @@ pub const Node = union(enum) { pseudo_section: PseudoSectionMapIndex, object_section: ObjectSectionMapIndex, + input_section: InputSectionIndex, global: GlobalMapIndex, nav: NavMapIndex, uav: UavMapIndex, @@ -266,6 +280,46 @@ pub const Node = union(enum) { } }; + pub const InputIndex = enum(u32) { + _, + + pub fn path(ii: InputIndex, coff: *const Coff) std.Build.Cache.Path { + return coff.inputs.items[@intFromEnum(ii)].path; + } + + pub fn archiveName(ii: InputIndex, coff: *const Coff) ?[]const u8 { + return coff.inputs.items[@intFromEnum(ii)].archive_name; + } + + pub fn firstSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { + return coff.inputs.items[@intFromEnum(ii)].first_si; + } + + pub fn lastSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { + return coff.inputs.items[@intFromEnum(ii)].last_si; + } + }; + + pub const InputSectionIndex = enum(u32) { + _, + + pub fn input(isi: InputSectionIndex, coff: *const Coff) InputIndex { + return coff.input_sections.items[@intFromEnum(isi)].ii; + } + + pub fn fileLocation(isi: InputSectionIndex, coff: *const Coff) MappedFile.Node.FileLocation { + return coff.input_sections.items[@intFromEnum(isi)].file_location; + } + + pub fn symbol(isi: InputSectionIndex, coff: *const Coff) Symbol.Index { + return coff.input_sections.items[@intFromEnum(isi)].si; + } + + pub fn lastSymbol(isi: InputSectionIndex, coff: *const Coff) Symbol.Index { + return coff.input_sections.items[@intFromEnum(isi)].last_si; + } + }; + pub const LazyMapRef = struct { kind: link.File.LazySymbol.Kind, index: u32, @@ -648,15 +702,15 @@ pub const Section = struct { si: Symbol.Index, relocation_table_ni: MappedFile.Node.Index, - pub const RelocationIndex = enum(u32) { + pub const RelocationIndex = enum(u16) { none, _, - pub fn wrap(i: ?u32) RelocationIndex { + pub fn wrap(i: ?u16) RelocationIndex { return @enumFromInt((i orelse return .none) + 1); } - pub fn unwrap(sri: RelocationIndex) ?u32 { + pub fn unwrap(sri: RelocationIndex) ?u16 { return switch (sri) { .none => null, _ => @intFromEnum(sri) - 1, @@ -680,7 +734,12 @@ pub const GlobalName = struct { name: String, lib_name: String.Optional }; pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, - size: u32, + value: union { + /// For generated symbols, this is their size + size: u32, + /// For globals from input sections, this is the offset within the input section + input_offset: u32, + }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, /// Relocations targeting this symbol @@ -704,6 +763,10 @@ pub const Symbol = struct { return sn.section(coff).si; } + pub fn name(sn: SectionNumber, coff: *const Coff) String { + return coff.section_table.keys()[sn.toIndex()]; + } + pub fn section(sn: SectionNumber, coff: *const Coff) *Section { return &coff.section_table.values()[sn.toIndex()]; } @@ -732,6 +795,10 @@ pub const Symbol = struct { return ni; } + pub fn next(si: Symbol.Index) Symbol.Index { + return @enumFromInt(@intFromEnum(si) + 1); + } + pub fn knownString(si: Symbol.Index) String.Optional { return switch (si) { .null, _ => .none, @@ -742,6 +809,10 @@ pub const Symbol = struct { pub fn flushMoved(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); sym.rva = coff.computeNodeRva(sym.ni); + if (sym.gmi != .none and coff.getNode(sym.ni) == .input_section) { + // Symbols in input sections share a ni with their section + sym.rva += sym.value.input_offset; + } si.applyLocationRelocs(coff); si.applyTargetRelocs(coff); } @@ -761,13 +832,18 @@ pub const Symbol = struct { pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); - for (coff.relocs.items[@intFromEnum(sym.loc_relocs)..]) |*reloc| { - if (reloc.loc != si) break; - if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore( - &entry.virtual_address, - @intCast(coff.computeNodeSectionOffset(sym.ni) + reloc.offset), - ); - reloc.apply(coff); + switch (sym.loc_relocs) { + .none => {}, + else => |loc_relocs| { + for (coff.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| { + if (reloc.loc != si) break; + if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore( + &entry.virtual_address, + @intCast(coff.computeSymbolSectionOffset(sym) + reloc.offset), + ); + reloc.apply(coff); + } + }, } } @@ -783,11 +859,16 @@ pub const Symbol = struct { pub fn deleteLocationRelocs(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); - for (coff.relocs.items[@intFromEnum(sym.loc_relocs)..]) |*reloc| { - if (reloc.loc != si) break; - reloc.delete(coff); + switch (sym.loc_relocs) { + .none => {}, + else => |loc_relocs| { + for (coff.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| { + if (reloc.loc != si) break; + reloc.delete(coff); + } + sym.loc_relocs = .none; + }, } - sym.loc_relocs = .none; } }; @@ -797,14 +878,20 @@ pub const Symbol = struct { }; pub const Reloc = extern struct { + offset: u64, + addend: i64, type: Reloc.Type, + sri: Section.RelocationIndex, prev: Reloc.Index, next: Reloc.Index, loc: Symbol.Index, target: Symbol.Index, - sri: Section.RelocationIndex, - offset: u64, - addend: i64, + flags: packed struct(u8) { + // Indicates the addend is not known and should be recovered from the location itself. + // COFF relocation tables don't encode the addend, only the location. + recover_addend: bool, + _: u7 = 0, + }, pub const Type = extern union { AMD64: std.coff.IMAGE.REL.AMD64, @@ -827,7 +914,7 @@ pub const Reloc = extern struct { } }; - pub fn apply(reloc: *const Reloc, coff: *Coff) void { + pub fn apply(reloc: *Reloc, coff: *Coff) void { const loc_sym = reloc.loc.get(coff); switch (loc_sym.ni) { .none => return, @@ -836,9 +923,11 @@ pub const Reloc = extern struct { const loc_slice = loc_sym.ni.slice(&coff.mf)[@intCast(reloc.offset)..]; const target_endian = coff.targetEndian(); + const target_machine = coff.targetLoad(&coff.headerPtr().machine); if (!coff.isImage()) { - switch (coff.targetLoad(&coff.headerPtr().machine)) { + assert(!reloc.flags.recover_addend); + switch (target_machine) { else => |machine| @panic(@tagName(machine)), .AMD64 => switch (reloc.type.AMD64) { else => |kind| @panic(@tagName(kind)), @@ -890,6 +979,54 @@ pub const Reloc = extern struct { } return; + } else if (reloc.flags.recover_addend) { + reloc.flags.recover_addend = false; + reloc.addend = switch (target_machine) { + else => |machine| @panic(@tagName(machine)), + .AMD64 => switch (reloc.type.AMD64) { + else => |kind| @panic(@tagName(kind)), + .ABSOLUTE => 0, + .ADDR64 => @bitCast(std.mem.readInt( + u64, + loc_slice[0..8], + target_endian, + )), + .ADDR32, + .ADDR32NB, + .REL32, + .REL32_1, + .REL32_2, + .REL32_3, + .REL32_4, + .REL32_5, + .SECREL, + => std.mem.readInt( + u32, + loc_slice[0..4], + target_endian, + ), + }, + .I386 => switch (reloc.type.I386) { + else => |kind| @panic(@tagName(kind)), + .ABSOLUTE => 0, + .DIR16, + .REL16, + => std.mem.readInt( + u16, + loc_slice[0..2], + target_endian, + ), + .DIR32, + .DIR32NB, + .REL32, + .SECREL, + => std.mem.readInt( + u32, + loc_slice[0..4], + target_endian, + ), + }, + }; } const target_sym = reloc.target.get(coff); @@ -899,8 +1036,7 @@ pub const Reloc = extern struct { } const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend)); - - switch (coff.targetLoad(&coff.headerPtr().machine)) { + switch (target_machine) { else => |machine| @panic(@tagName(machine)), .AMD64 => switch (reloc.type.AMD64) { else => |kind| @panic(@tagName(kind)), @@ -962,7 +1098,7 @@ pub const Reloc = extern struct { .SECREL => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(coff.computeNodeSectionOffset(target_sym.ni) + reloc.addend), + @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), target_endian, ), }, @@ -1002,7 +1138,7 @@ pub const Reloc = extern struct { .SECREL => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(coff.computeNodeSectionOffset(target_sym.ni) + reloc.addend), + @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), target_endian, ), }, @@ -1133,6 +1269,9 @@ fn create( .pending = .empty, .pending_shrink = false, }, + .inputs = .empty, + .input_sections = .empty, + .input_section_pending_index = 0, .strings = .empty, .string_bytes = .empty, .section_table = .empty, @@ -1154,6 +1293,7 @@ fn create( .synth_prog_node = .none, .symbol_prog_node = .none, .member_prog_node = .none, + .input_prog_node = .none, .dump_snapshot = options.enable_link_snapshots, }; errdefer coff.deinit(); @@ -1561,7 +1701,7 @@ fn initHeaders( coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, .rva = 0, - .size = 0, + .value = .{ .size = 0 }, .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, @@ -1701,12 +1841,18 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { coff.symbol_prog_node = prog_node.start("Symbols", coff.symbol_table.pending.count()); coff.member_prog_node = prog_node.start("Members", coff.pending_members.count()); } + coff.input_prog_node = prog_node.start( + "Inputs", + coff.input_sections.items.len - coff.input_section_pending_index, + ); coff.mf.update_prog_node = prog_node.start("Relocations", coff.mf.updates.items.len); } pub fn endProgress(coff: *Coff) void { coff.mf.update_prog_node.end(); coff.mf.update_prog_node = .none; + coff.input_prog_node.end(); + coff.input_prog_node = .none; if (!isImage(coff)) { coff.member_prog_node.end(); coff.member_prog_node = .none; @@ -1736,11 +1882,11 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .section_table, .export_name_table, .placeholder, - .symbol_table, .string_table, .relocation_table, .relocation_table_entry, + .input_section, => unreachable, .image_section => |si| si, .import_directory_table => break :parent_rva coff.targetLoad( @@ -1781,9 +1927,12 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { const offset, _ = ni.location(&coff.mf).resolve(&coff.mf); return @intCast(parent_rva + offset); } -fn computeNodeSectionOffset(coff: *Coff, ni: MappedFile.Node.Index) u32 { - var section_offset: u32 = 0; - var parent_ni = ni; +fn computeSymbolSectionOffset(coff: *Coff, sym: *const Symbol) u32 { + var section_offset: u32 = if (sym.gmi != .none and coff.getNode(sym.ni) == .input_section) + sym.value.input_offset + else + 0; + var parent_ni = sym.ni; while (true) { const offset, _ = parent_ni.location(&coff.mf).resolve(&coff.mf); section_offset += @intCast(offset); @@ -1981,7 +2130,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { defer coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, .rva = 0, - .size = 0, + .value = .{ .size = 0 }, .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, @@ -2003,6 +2152,15 @@ 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 { + if (coff.strings.getKeyAdapted( + string, + std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes }, + )) |key| + return @enumFromInt(key) + else + return null; +} /// If the name does not fit in the symbol header, adds it to the symbol table string table. /// If the caller knows this name already has a String associated with it, they can avoid @@ -2105,7 +2263,7 @@ fn navSection( const ip = &zcu.intern_pool; const default: String, const attributes: ObjectSectionAttributes = if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{ - .@".tls$", .{ .read = true, .write = !coff.isImage() }, + .@".tls$", .{ .read = true, .write = true }, } else if (ip.isFunctionType(nav_resolved.type)) .{ .@".text", .{ .read = true, .execute = true }, } else if (nav_resolved.@"const") .{ @@ -2189,7 +2347,7 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. @enumFromInt(@intFromEnum(reloc_info.parent.atom_index)), reloc_info.offset, target_si, - reloc_info.addend, + .{ .known = reloc_info.addend }, switch (coff.targetLoad(&coff.headerPtr().machine)) { else => unreachable, .AMD64 => .{ .AMD64 = .ADDR64 }, @@ -2467,19 +2625,40 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }; coff.targetStore(&entry.value, switch (sym.section_number) { - .UNDEFINED => sym.size, + .UNDEFINED => sym.value.size, .ABSOLUTE, .DEBUG, => unreachable, else => switch (coff.getNode(sym.ni)) { .image_section => 0, - else => coff.computeNodeSectionOffset(sym.ni), + else => coff.computeSymbolSectionOffset(sym), }, }); log.debug("updateSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); } +fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void { + const file_loc = isi.fileLocation(coff); + if (file_loc.size == 0) return; + const comp = coff.base.comp; + const io = comp.io; + const gpa = comp.gpa; + const ii = isi.input(coff); + const path = ii.path(coff); + const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); + defer file.close(io); + var fr = file.reader(io, &.{}); + try fr.seekTo(file_loc.offset); + var nw: MappedFile.Node.Writer = undefined; + const si = isi.symbol(coff); + si.node(coff).writer(&coff.mf, gpa, &nw); + defer nw.deinit(); + if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) + return error.EndOfStream; + si.applyLocationRelocs(coff); +} + fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { assert(coff.base.comp.zcu != null); @@ -2612,7 +2791,7 @@ fn pseudoSectionMapIndex( const gpa = coff.base.comp.gpa; const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name); const psmi: Node.PseudoSectionMapIndex = @enumFromInt(pseudo_section_gop.index); - if (!pseudo_section_gop.found_existing) { + const sn = if (!pseudo_section_gop.found_existing) sn: { const default_parent: Symbol.Index = if (attributes.execute) .text else if (attributes.write) @@ -2626,11 +2805,10 @@ fn pseudoSectionMapIndex( default_parent.knownString().toSlice(coff).?, )) default_parent - else if (coff.section_table.get(name)) |section| parent: { - const header = section.si.get(coff).section_number.header(coff); - try coff.verifyParentSectionAttributes(name, name, .fromFlags(header.flags), attributes); - break :parent section.si; - } else try coff.addSection(name, attributes.asFlags()); + else if (coff.section_table.get(name)) |section| + section.si + else + try coff.addSection(name, attributes.asFlags()); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -2644,9 +2822,30 @@ fn pseudoSectionMapIndex( assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); coff.nodes.appendAssumeCapacity(.{ .pseudo_section = psmi }); - } + break :sn sym.section_number; + } else pseudo_section_gop.value_ptr.get(coff).section_number; + + try coff.verifyParentSectionAttributes( + .pseudo, + sn.name(coff), + name, + .fromFlags(sn.header(coff).flags), + attributes, + ); + return psmi; } + +fn objectSectionParentName(coff: *Coff, name: []const u8) []const u8 { + // In images we want to sort object sections into the final root section name. + // Otherwise, we want to keep the full name so that this sort can occur correctly when + // the object is finally linked into an image. + return if (coff.isImage()) + name[0 .. std.mem.indexOfScalar(u8, name, '$') orelse name.len] + else + name; +} + fn objectSectionMapIndex( coff: *Coff, name: String, @@ -2654,23 +2853,20 @@ fn objectSectionMapIndex( attributes: ObjectSectionAttributes, ) !Node.ObjectSectionMapIndex { const gpa = coff.base.comp.gpa; + const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name.toSlice(coff), ".tls")) attr: { + // In images, the .tls section is a read-only template + var attr = attributes; + attr.write = false; + break :attr attr; + } else attributes; + const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index); - if (!object_section_gop.found_existing) { + const sn = if (!object_section_gop.found_existing) sn: { try coff.ensureUnusedStringCapacity(name.toSlice(coff).len); const name_slice = name.toSlice(coff); - const prefix_index = std.mem.indexOfScalar(u8, name_slice, '$') orelse name_slice.len; - const parent_name = coff.getOrPutStringAssumeCapacity(if (coff.isImage()) - name_slice[0..prefix_index] - else - name_slice[0..@min(prefix_index + 1, name_slice.len)]); - const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, attributes)).symbol(coff); - try coff.verifyParentSectionAttributes( - parent_name, - name, - .fromFlags(parent.get(coff).section_number.header(coff).flags), - attributes, - ); + const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice)); + const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); const parent_ni = parent.node(coff); @@ -2704,12 +2900,23 @@ fn objectSectionMapIndex( assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); coff.nodes.appendAssumeCapacity(.{ .object_section = osmi }); - } + break :sn sym.section_number; + } else object_section_gop.value_ptr.get(coff).section_number; + + try coff.verifyParentSectionAttributes( + .object, + sn.name(coff), + name, + .fromFlags(sn.header(coff).flags), + effective_attributes, + ); + return osmi; } fn verifyParentSectionAttributes( coff: *Coff, + kind: enum { pseudo, object }, parent_name: String, child_name: String, parent_attrs: ObjectSectionAttributes, @@ -2718,18 +2925,25 @@ fn verifyParentSectionAttributes( if (parent_attrs == child_attrs) return; const fields = std.meta.fields(ObjectSectionAttributes); - var err = try coff.base.comp.link_diags.addErrorWithNotes(fields.len); - try err.addMsg("object '{s}' was placed in parent section '{s}' with mismatched flags", .{ + const BackingT = @typeInfo(ObjectSectionAttributes).@"struct".backing_integer.?; + const num_notes = @popCount(@as(BackingT, @bitCast(parent_attrs)) ^ @as(BackingT, @bitCast(child_attrs))); + var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes); + try err.addMsg("{t} section '{s}' was placed in parent section '{s}' with mismatched flags", .{ + kind, child_name.toSlice(coff), parent_name.toSlice(coff), }); inline for (fields) |field| { - err.addNote("{s}: parent = {d} child = {d}", .{ - field.name, - @intFromBool(@field(child_attrs, field.name)), - @intFromBool(@field(parent_attrs, field.name)), - }); + if (@field(child_attrs, field.name) != @field(parent_attrs, field.name)) { + err.addNote("flags.{s} was {d} in {s}, but {d} in {s}", .{ + field.name, + @intFromBool(@field(child_attrs, field.name)), + child_name.toSlice(coff), + @intFromBool(@field(parent_attrs, field.name)), + parent_name.toSlice(coff), + }); + } } return error.LinkFailure; @@ -2740,13 +2954,24 @@ pub fn addReloc( loc_si: Symbol.Index, offset: u64, target_si: Symbol.Index, - addend: i64, + addend: union(enum) { + known: i64, + pending: void, + }, @"type": Reloc.Type, ) !void { const gpa = coff.base.comp.gpa; const target = target_si.get(coff); - log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d})", .{ loc_si, loc_si.get(coff).section_number, offset, target_si, target_si.get(coff).section_number, addend }); + log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d}{s})", .{ + loc_si, + loc_si.get(coff).section_number, + offset, + target_si, + target_si.get(coff).section_number, + if (addend == .pending) 0 else addend.known, + if (addend == .pending) "p" else "k", + }); try coff.relocs.ensureUnusedCapacity(gpa, 1); @@ -2817,7 +3042,10 @@ pub fn addReloc( .target = target_si, .sri = sri, .offset = offset, - .addend = addend, + .addend = if (addend == .pending) 0 else addend.known, + .flags = .{ + .recover_addend = addend == .pending, + }, }; switch (target.target_relocs) { .none => {}, @@ -2869,10 +3097,34 @@ pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError || fn fmtArchiveNameString(archiveName: ?[]const u8) std.fmt.Alt(?[]const u8, archiveNameStringEscape) { return .{ .data = archiveName }; } + fn archiveNameStringEscape(archiveName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { try w.print("({f})", .{std.zig.fmtString(archiveName orelse return)}); } +fn inputSectionHeaderNameSlice( + coff: *Coff, + header: *const std.coff.SectionHeader, + string_table: []const u8, + path: std.Build.Cache.Path, + section_i: usize, +) ![]const u8 { + const diags = &coff.base.comp.link_diags; + return if (header.name[0] == '/') name: { + const offset_str = std.mem.sliceTo(header.name[1..], 0); + const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch + return diags.failParse(path, "ill-formed section name in section {d}: '{s}'", .{ + section_i, + header.name[0 .. offset_str.len + 1], + }); + + if (name_offset > string_table.len) + return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset }); + + break :name std.mem.sliceTo(string_table[name_offset..], 0); + } else std.mem.sliceTo(&header.name, 0); +} + fn loadObject( coff: *Coff, path: std.Build.Cache.Path, @@ -2890,6 +3142,7 @@ fn loadObject( assert(!coff.isObj()); log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtArchiveNameString(archive_name) }); + const header = try r.peekStruct(std.coff.Header, coff.targetEndian()); if (header.machine != target.toCoffMachine()) return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{ @@ -2927,6 +3180,16 @@ fn loadObject( symbol_table_end + string_table_len > fl.size) return diags.failParse(path, "bad string table", .{}); + const ii: Node.InputIndex = @enumFromInt(coff.inputs.items.len); + try coff.inputs.ensureUnusedCapacity(gpa, 1); + const input = coff.inputs.addOneAssumeCapacity(); + input.* = .{ + .path = path, + .archive_name = if (archive_name) |m| try gpa.dupe(u8, m) else null, + .first_si = .null, + .last_si = .null, + }; + const string_table = string_table: { const string_table = try gpa.alloc(u8, string_table_len); errdefer gpa.free(string_table); @@ -2942,38 +3205,34 @@ fn loadObject( const InputSection = struct { header: std.coff.SectionHeader, - psmi: Node.PseudoSectionMapIndex, + name: String, + si: Symbol.Index, }; - try fr.seekTo(fl.offset + @sizeOf(std.coff.Header)); const sections: []const InputSection = if (coff.isImage()) sections: { const sections = try gpa.alloc(InputSection, header.number_of_sections); errdefer gpa.free(sections); + var num_input_sections: u16 = 0; + var reqd_object_sections: std.AutoArrayHashMapUnmanaged(String, void) = .empty; + defer reqd_object_sections.deinit(gpa); + var reqd_pseudo_sections: std.StringArrayHashMapUnmanaged(void) = .empty; + defer reqd_pseudo_sections.deinit(gpa); + try reqd_object_sections.ensureUnusedCapacity(gpa, sections.len); + try reqd_pseudo_sections.ensureUnusedCapacity(gpa, sections.len); + + try fr.seekTo(fl.offset + @sizeOf(std.coff.Header)); for (sections, 0..) |*section, section_i| { - section.header = try r.takeStruct(std.coff.SectionHeader, target_endian); - if (section.header.flags.LNK_INFO) { - if (std.mem.eql(u8, §ion.header.name, ".drectve")) - return diags.failParse(path, "TODO handle arguments in .drectve section", .{}); - - continue; - } - - if (section.header.flags.LNK_REMOVE or - section.header.flags.MEM_DISCARDABLE) - { - // TODO: Merge .debug$* sections and output to PDB - continue; - } - - if (section.header.flags.LNK_COMDAT) - // This will be necessary if we do the equivalent of /Gy for compiler-rt - return diags.failParse(path, "TODO handle COMDAT sections in input objects", .{}); + section.* = .{ + .header = try r.takeStruct(std.coff.SectionHeader, target_endian), + .name = undefined, + .si = .null, + }; const section_name_slice = if (section.header.name[0] == '/') name: { const offset_str = std.mem.sliceTo(section.header.name[1..], 0); const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch - return diags.failParse(path, "ill-formed section name in section {d}: '{s}'", .{ + return diags.failParse(path, "ill-formed section name offset in section {d}: '{s}'", .{ section_i, section.header.name[0 .. offset_str.len + 1], }); @@ -2983,26 +3242,137 @@ fn loadObject( break :name std.mem.sliceTo(string_table[name_offset..], 0); } else std.mem.sliceTo(§ion.header.name, 0); + section.name = coff.getOrPutStringAssumeCapacity(section_name_slice); - const section_name = coff.getOrPutStringAssumeCapacity(section_name_slice); - const osmi = try coff.objectSectionMapIndex( - section_name, - if (section.header.flags.ALIGN.toByteUnits()) |align_bytes| + if (section.header.pointer_to_linenumbers + + section.header.number_of_linenumbers * std.coff.LineNumber.sizeOf() > fl.size) + return diags.failParse(path, "bad line numbers location in section {d} `{s}`", .{ + section_i, + section_name_slice, + }); + + if (section.header.pointer_to_relocations + + section.header.number_of_relocations * std.coff.Relocation.sizeOf() > fl.size) + return diags.failParse(path, "bad relocations location in section {d} `{s}`", .{ + section_i, + section_name_slice, + }); + + if (section.header.pointer_to_raw_data + section.header.size_of_raw_data > fl.size) + return diags.failParse(path, "bad raw data location in section {d} `{s}`", .{ + section_i, + section_name_slice, + }); + + if (section.header.flags.LNK_REMOVE or + section.header.flags.MEM_DISCARDABLE) + { + // TODO: Merge .debug$* sections and output to PDB + continue; + } + + num_input_sections += 1; + _ = reqd_object_sections.getOrPutAssumeCapacity(section.name); + _ = reqd_pseudo_sections.getOrPutAssumeCapacity( + coff.objectSectionParentName(section.name.toSlice(coff)), + ); + } + + var symbol_capacity: u16 = num_input_sections; + var node_capacity: u16 = 0; + { + var iter = reqd_object_sections.count(); + while (iter > 0) { + iter -= 1; + if (coff.object_section_table.contains(reqd_object_sections.keys()[iter])) + reqd_object_sections.swapRemoveAt(iter); + } + + node_capacity += @intCast(reqd_object_sections.count()); + symbol_capacity += @intCast(reqd_object_sections.count()); + } + + { + var iter = reqd_pseudo_sections.count(); + while (iter > 0) { + // TODO: Track the extra number of strings and their length and reserve? These have not been reserved as + // part of the ensureManyUnusedStringCapacity call above + iter -= 1; + const name = coff.getString(reqd_pseudo_sections.keys()[iter]) orelse continue; + if (coff.pseudo_section_table.contains(name)) + reqd_pseudo_sections.swapRemoveAt(iter); + } + + node_capacity += @intCast(reqd_pseudo_sections.count()); + symbol_capacity += @intCast(reqd_pseudo_sections.count()); + } + + try coff.nodes.ensureUnusedCapacity(gpa, node_capacity); + try coff.symbols.ensureUnusedCapacity(gpa, symbol_capacity + num_input_sections); + try coff.input_sections.ensureUnusedCapacity(gpa, num_input_sections); + + for (sections) |*section| { + if (section.header.flags.LNK_INFO) { + if (std.mem.eql(u8, §ion.header.name, ".drectve")) { + try fr.seekTo(fl.offset + section.header.pointer_to_raw_data); + var buf: [128]u8 = undefined; + var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf); + while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) { + error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}), + else => |e| return e, + }) |arg| { + // Microsoft tools emit 3 space characters into this section even with /Zl + if (arg.len > 0) + return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg}); + } + } + + continue; + } + + if (section.header.flags.LNK_REMOVE or + section.header.flags.MEM_DISCARDABLE) + { + continue; + } + + if (section.header.flags.LNK_COMDAT) + // This will be necessary if we do the equivalent of /Gy for compiler-rt + return diags.failParse(path, "TODO handle COMDAT sections in input objects", .{}); + + log.debug("loadInputSection({s})", .{section.name.toSlice(coff)}); + + const parent_osmi = try coff.objectSectionMapIndex( + section.name, + coff.mf.flags.block_size, + .fromFlags(section.header.flags), + ); + const parent_si = parent_osmi.symbol(coff); + const ni = try coff.mf.addLastChildNode(gpa, parent_si.node(coff), .{ + .size = section.header.size_of_raw_data, + .alignment = if (section.header.flags.ALIGN.toByteUnits()) |align_bytes| .fromByteUnits(align_bytes) else .@"1", - .fromFlags(section.header.flags), - ); + .moved = true, + }); + coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) }); - _ = osmi; + section.si = coff.addSymbolAssumeCapacity(); + const sym = section.si.get(coff); + sym.ni = ni; + sym.section_number = parent_si.get(coff).section_number; - // TODO: Decide to merge this section - // TODO: Map flags (might need to figure out a better tls flag?) - - //coff.objectSectionMapIndex(name: String, alignment: Alignment, attributes: ObjectSectionAttributes) - - // TODO: Load relocations, update for new offset? Or can just work with the object section parent? + coff.input_sections.addOneAssumeCapacity().* = .{ + .ii = ii, + .si = section.si, + .file_location = .{ + .offset = fl.offset + section.header.pointer_to_raw_data, + .size = section.header.size_of_raw_data, + }, + }; + coff.synth_prog_node.increaseEstimatedTotalItems(1); } break :sections sections; @@ -3019,38 +3389,42 @@ fn loadObject( const member = mi.get(coff); try member.initHeader(coff, path_str, header.time_date_stamp); + // TODO: This could be deferred to an idle task? + { var nw: MappedFile.Node.Writer = undefined; member.content_ni.writer(&coff.mf, gpa, &nw); defer nw.deinit(); try fr.seekTo(fl.offset); - try r.streamExact(&nw.interface, fl.size); + if (try nw.interface.sendFileAll(fr, .limited64(fl.size)) != fl.size) + return error.EndOfStream; } break :mi mi; } else undefined; + // TODO: Also reserve memory for the symbols / globals / relocs within each section + try fr.seekTo(fl.offset + header.pointer_to_symbol_table); - const symbol_size = std.coff.Symbol.sizeOf(); + const symbol_size = comptime std.coff.Symbol.sizeOf(); + var symbols: std.ArrayList(Symbol.Index) = .empty; + try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); + + const first_si = coff.symbols.items.len; var symbol_ix: u32 = 0; while (symbol_ix < header.number_of_symbols) { - const symbol: *align(2) std.coff.Symbol = @ptrCast(@alignCast(try r.take(symbol_size))); + var symbol: std.coff.Symbol = undefined; + @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], try r.take(symbol_size)); + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.Symbol, &symbol); + defer { r.toss(symbol.number_of_aux_symbols * symbol_size); symbol_ix += symbol.number_of_aux_symbols + 1; } - switch (symbol.section_number) { - .UNDEFINED, .ABSOLUTE, .DEBUG => continue, - else => switch (symbol.storage_class) { - .STATIC => if (symbol.value == 0) continue, - .EXTERNAL => {}, - else => continue, - }, - } - const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], target_endian); if (index >= string_table.len) @@ -3058,21 +3432,125 @@ fn loadObject( break :name string_table[index..]; } else &symbol.name, 0); - // Section numbers are 1-based here - if (!is_archive and @intFromEnum(symbol.section_number) > sections.len) - return diags.failParse(path, "bad section number {d} for '{s}'", .{ symbol.section_number, name }); + const si = symbols.addOneAssumeCapacity(); + si.* = .null; + + switch (symbol.section_number) { + .UNDEFINED, .ABSOLUTE, .DEBUG => continue, + else => switch (symbol.storage_class) { + .STATIC => if (symbol.value == 0 and symbol.type == std.coff.SymType{ + .complex_type = .NULL, + .base_type = .NULL, + }) { + if (symbol.number_of_aux_symbols != 1) + return diags.failParse(path, "invalid number of aux symbols for section {d}: {d}", .{ + symbol_ix, + symbol.number_of_aux_symbols, + }); + + var section_def: std.coff.SectionDefinition = undefined; + @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], try r.peek(symbol_size)); + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def); + + // TODO: Extract the COMDAT section info + + if (section_def.number > sections.len) + return diags.failParse( + path, + "section symbol for '{s}' contained an out of bounds section number: {d}", + .{ name, section_def.number }, + ); + + // It's valid for this to not match the symbol's section number (ie. .drectve sets this) + if (section_def.number == 0) + continue; + + const section = §ions[section_def.number - 1]; + if (section_def.number_of_relocations != section.header.number_of_relocations) + return diags.failParse( + path, + "section symbol for '{s}' relocation count did not match section header: {d} vs {d}", + .{ name, section_def.number_of_relocations, section.header.number_of_relocations }, + ); + + if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) + return diags.failParse( + path, + "section symbol for '{s}' line number count did not match section header: {d} vs {d}", + .{ name, section_def.number_of_linenumbers, section.header.number_of_linenumbers }, + ); + + si.* = section.si; + continue; + }, + .EXTERNAL => {}, + else => continue, + }, + } if (is_archive) { try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); continue; } + // Section numbers are 1-based here + if (@intFromEnum(symbol.section_number) <= 0 or @intFromEnum(symbol.section_number) > sections.len) + return diags.failParse(path, "bad section number {d} for '{s}'", .{ symbol.section_number, name }); + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); + // TODO: Support weak symbols if (global_gop.found_existing) return diags.failParse(path, "multiple definitions of '{s}'", .{name}); + si.* = global_gop.value_ptr.*; - // TODO: Get the sym and set the ni to point to wherever it was copied in the pseudo section - // TODO: May need to cache offsets and determine symbol sizes later (once we can sort by section offset) + const section = sections[@intCast(@intFromEnum(symbol.section_number) - 1)]; + const section_sym = section.si.get(coff); + + const sym = si.get(coff); + sym.ni = section_sym.ni; + sym.value = .{ .input_offset = symbol.value }; + sym.section_number = section_sym.section_number; + } + + if (coff.symbols.items.len > first_si) { + input.first_si = @enumFromInt(first_si); + input.last_si = @enumFromInt(coff.symbols.items.len - 1); + } + + const relocation_size = std.coff.Relocation.sizeOf(); + for (sections) |section| { + if (section.si == .null) continue; + + const loc_sym = section.si.get(coff); + assert(loc_sym.loc_relocs == .none); + loc_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + + if (section.header.number_of_relocations == 0) continue; + + try coff.relocs.ensureUnusedCapacity(gpa, section.header.number_of_relocations); + try fr.seekTo(fl.offset + section.header.pointer_to_relocations); + for (0..section.header.number_of_relocations) |reloc_i| { + var reloc: std.coff.Relocation = undefined; + @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size)); + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.Relocation, &reloc); + + if (reloc.symbol_table_index >= symbols.items.len) + return diags.failParse( + path, + "relocation {d} in section '{s}' targets invalid symbol index {d}", + .{ reloc_i, section.name.toSlice(coff), reloc.symbol_table_index }, + ); + + try coff.addReloc( + section.si, + reloc.virtual_address - section.header.virtual_address, + symbols.items[reloc.symbol_table_index], + .pending, + @bitCast(reloc.type), // TODO: Checks on this cast? + ); + } } } @@ -3184,13 +3662,13 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).size = @intCast(nw.interface.end); + si.get(coff).value.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } // TODO: Did my MappedFile resize change affect this? if (nav.resolved.?.@"linksection".unwrap()) |_| { - try ni.resize(&coff.mf, gpa, si.get(coff).size); + try ni.resize(&coff.mf, gpa, si.get(coff).value.size); var parent_ni = ni; while (true) { parent_ni = parent_ni.parent(&coff.mf); @@ -3318,7 +3796,7 @@ fn updateFuncInner( error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).size = @intCast(nw.interface.end); + si.get(coff).value.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } @@ -3543,6 +4021,28 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; } + // TODO: Idle task for flushing obj into lib? + if (coff.input_section_pending_index < coff.input_sections.items.len) { + const isi: Node.InputSectionIndex = @enumFromInt(coff.input_section_pending_index); + coff.input_section_pending_index += 1; + const sub_prog_node = coff.idleProgNode(tid, coff.input_prog_node, coff.getNode(isi.symbol(coff).node(coff))); + defer sub_prog_node.end(); + coff.flushInputSection(isi) catch |err| switch (err) { + else => |e| { + const ii = isi.input(coff); + return comp.link_diags.fail( + "linker failed to read input section '{s}' from \"{f}{f}\": {t}", + .{ + isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(ii.archiveName(coff)), + e, + }, + ); + }, + }; + break :task; + } while (coff.mf.updates.pop()) |ni| { const clean_moved = ni.cleanMoved(&coff.mf); const clean_resized = ni.cleanResized(&coff.mf); @@ -3608,6 +4108,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.globals.count() > coff.global_pending_index) return true; if (coff.symbol_table.pending.count() > 0) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; + if (coff.input_sections.items.len > coff.input_section_pending_index) return true; if (coff.mf.updates.items.len > 0) return true; if (coff.pending_members.count() > 0) return true; if (coff.export_table.pending_sort) return true; @@ -3626,6 +4127,14 @@ fn idleProgNode( else => |tag| @tagName(tag), .image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0), inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff), + .input_section => |isi| { + const ii = isi.input(coff); + break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{ + ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(ii.archiveName(coff)), + isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + }) catch &name; + }, .global => |gmi| gmi.globalName(coff).name.toSlice(coff), .nav => |nmi| { const ip = &coff.base.comp.zcu.?.intern_pool; @@ -3699,7 +4208,7 @@ fn flushUav( error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).size = @intCast(nw.interface.end); + si.get(coff).value.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } @@ -3864,12 +4373,12 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { }); @memcpy(ni.slice(&coff.mf)[0..init.len], &init); sym.ni = ni; - sym.size = init.len; + sym.value.size = init.len; try coff.addReloc( si, init.len - 4, gop.value_ptr.import_address_table_si, - @intCast(addr_size * import_symbol_index), + .{ .known = @intCast(addr_size * import_symbol_index) }, .{ .AMD64 = .REL32 }, ); }, @@ -3929,7 +4438,7 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).size = @intCast(nw.interface.end); + si.get(coff).value.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } @@ -3992,6 +4501,15 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { @intCast(file_offset), ); }, + .input_section => |isi| { + const ii = isi.input(coff); + var si = ii.firstSymbol(coff); + const last_si = ii.lastSymbol(coff); + while (@intFromEnum(si) <= @intFromEnum(last_si)) : (si = si.next()) { + if (si.get(coff).ni != ni) continue; + si.flushMoved(coff); + } + }, .import_directory_table => coff.targetStore( &coff.dataDirectoryPtr(.IMPORT).virtual_address, coff.computeNodeRva(ni), @@ -4204,6 +4722,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ); } }, + .input_section => {}, .import_directory_table => coff.targetStore( &coff.dataDirectoryPtr(.IMPORT).size, @intCast(size), @@ -4228,7 +4747,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ); } - smi.symbol(coff).get(coff).size = @intCast(size); + smi.symbol(coff).get(coff).value.size = @intCast(size); }, .global, .nav, @@ -4398,7 +4917,7 @@ fn updateExportsInner( const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; - export_sym.size = exported_sym.size; + export_sym.value.size = exported_sym.value.size; export_sym.section_number = exported_sym.section_number; defer export_si.applyTargetRelocs(coff); @@ -4407,7 +4926,7 @@ fn updateExportsInner( coff.optionalHeaderStandardPtr().address_of_entry_point = exported_sym.rva; } else if (@"export".opts.name.eqlSlice("_tls_used", ip)) { const tls_directory = coff.dataDirectoryPtr(.TLS); - tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.size }; + 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); } @@ -4492,7 +5011,7 @@ fn updateExportsInner( coff.export_table.export_address_table_si, @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index), export_si, - 0, + .{ .known = 0 }, .{ .AMD64 = .ADDR32NB }, ); } else { @@ -4541,6 +5060,14 @@ pub fn printNode( .image_section => |si| try w.print("({s})", .{ std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0), }), + .input_section => |isi| { + const ii = isi.input(coff); + try w.print("({f}{f}, {s})", .{ + ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(ii.archiveName(coff)), + isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + }); + }, .import_lookup_table, .import_address_table, .import_hint_name_table, -- 2.54.0 From ed42120d8b739794e884d924f92ef684325a4e34 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 16/94] Coff: Report undefined symbols - Report each undefined symbol (once per unique instance), up to a maximum of 4 - WIP on figuring out updating location relocs in inputs sections --- src/link/Coff.zig | 371 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 287 insertions(+), 84 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 6d519eb4b2bff5b07620243e5d6b57d05e5564d5..9c09f15dd22f229fa0578f72a029bb28e7ba6d19 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -737,7 +737,7 @@ pub const Symbol = struct { value: union { /// For generated symbols, this is their size size: u32, - /// For globals from input sections, this is the offset within the input section + /// For symbols from input sections, this is the offset within the input section input_offset: u32, }, /// Relocations contained within this symbol @@ -1322,11 +1322,15 @@ pub fn deinit(coff: *Coff) void { const gpa = coff.base.comp.gpa; coff.mf.deinit(gpa); coff.nodes.deinit(gpa); + coff.pending_members.deinit(gpa); + coff.lib_string_table.deinit(gpa); coff.long_names_table.entries.deinit(gpa); coff.import_table.entries.deinit(gpa); coff.export_table.entries.deinit(gpa); coff.symbol_table.strings.deinit(gpa); coff.symbol_table.pending.deinit(gpa); + coff.inputs.deinit(gpa); + coff.input_sections.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); coff.section_table.deinit(gpa); @@ -2145,6 +2149,13 @@ fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index { return si; } +fn initInputSectionSymbol(coff: *Coff, sym: *Symbol, section_si: Symbol.Index, value: u32) void { + const section_sym = section_si.get(coff); + sym.ni = section_sym.ni; + sym.value = .{ .input_offset = value }; + sym.section_number = section_sym.section_number; +} + fn getOrPutString(coff: *Coff, string: []const u8) !String { try coff.ensureUnusedStringCapacity(string.len); return coff.getOrPutStringAssumeCapacity(string); @@ -2240,7 +2251,12 @@ fn getOrPutGlobalSymbol( } pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { - return (try coff.getOrPutGlobalSymbol(opts)).value_ptr.*; + const gop = try coff.getOrPutGlobalSymbol(opts); + if (gop.found_existing) { + // TODO: Need to know if this is an export or extern, add to opts + } + + return gop.value_ptr.*; } pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void { @@ -2657,6 +2673,10 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void { if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; si.applyLocationRelocs(coff); + + // TODO: Problem is that if the sym is first seen as undef, it's si is in the range of the section + // that wants that symbol. but when the section that contains it is moved, the iteration doesn't see + // that symbol. } fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { @@ -3267,7 +3287,7 @@ fn loadObject( if (section.header.flags.LNK_REMOVE or section.header.flags.MEM_DISCARDABLE) { - // TODO: Merge .debug$* sections and output to PDB + // TODO: Convert .debug$* sections into PDB continue; } @@ -3340,8 +3360,6 @@ fn loadObject( // This will be necessary if we do the equivalent of /Gy for compiler-rt return diags.failParse(path, "TODO handle COMDAT sections in input objects", .{}); - log.debug("loadInputSection({s})", .{section.name.toSlice(coff)}); - const parent_osmi = try coff.objectSectionMapIndex( section.name, coff.mf.flags.block_size, @@ -3372,6 +3390,7 @@ fn loadObject( }, }; + log.debug("loadInputSection({s}) = {d}@{d}", .{ section.name.toSlice(coff), section.si, sym.section_number }); coff.synth_prog_node.increaseEstimatedTotalItems(1); } @@ -3428,89 +3447,141 @@ fn loadObject( const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], target_endian); if (index >= string_table.len) - return diags.failParse(path, "bad string offset for symbol {d}", .{symbol_ix}); + return diags.failParse(path, "bad string offset for symbol 0x{x}", .{symbol_ix}); break :name string_table[index..]; } else &symbol.name, 0); - const si = symbols.addOneAssumeCapacity(); - si.* = .null; - - switch (symbol.section_number) { - .UNDEFINED, .ABSOLUTE, .DEBUG => continue, - else => switch (symbol.storage_class) { - .STATIC => if (symbol.value == 0 and symbol.type == std.coff.SymType{ - .complex_type = .NULL, - .base_type = .NULL, - }) { - if (symbol.number_of_aux_symbols != 1) - return diags.failParse(path, "invalid number of aux symbols for section {d}: {d}", .{ - symbol_ix, - symbol.number_of_aux_symbols, - }); - - var section_def: std.coff.SectionDefinition = undefined; - @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], try r.peek(symbol_size)); - if (target_endian != native_endian) - std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def); - - // TODO: Extract the COMDAT section info - - if (section_def.number > sections.len) - return diags.failParse( - path, - "section symbol for '{s}' contained an out of bounds section number: {d}", - .{ name, section_def.number }, - ); - - // It's valid for this to not match the symbol's section number (ie. .drectve sets this) - if (section_def.number == 0) - continue; - - const section = §ions[section_def.number - 1]; - if (section_def.number_of_relocations != section.header.number_of_relocations) - return diags.failParse( - path, - "section symbol for '{s}' relocation count did not match section header: {d} vs {d}", - .{ name, section_def.number_of_relocations, section.header.number_of_relocations }, - ); - - if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) - return diags.failParse( - path, - "section symbol for '{s}' line number count did not match section header: {d} vs {d}", - .{ name, section_def.number_of_linenumbers, section.header.number_of_linenumbers }, - ); - - si.* = section.si; - continue; - }, - .EXTERNAL => {}, - else => continue, - }, - } + const si_slice = symbols.addManyAsSliceAssumeCapacity(1 + symbol.number_of_aux_symbols); + @memset(si_slice, .null); + + defer log.debug("loadInputSymbol({s}, 0x{x}) = {d}@{d}", .{ + name, + symbol.value, + si_slice[0], + if (si_slice[0] == .null) .UNDEFINED else si_slice[0].get(coff).section_number, + }); if (is_archive) { - try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); + if (symbol.storage_class == .EXTERNAL) + try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); + continue; } - // Section numbers are 1-based here - if (@intFromEnum(symbol.section_number) <= 0 or @intFromEnum(symbol.section_number) > sections.len) - return diags.failParse(path, "bad section number {d} for '{s}'", .{ symbol.section_number, name }); - - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); - // TODO: Support weak symbols - if (global_gop.found_existing) - return diags.failParse(path, "multiple definitions of '{s}'", .{name}); - si.* = global_gop.value_ptr.*; - - const section = sections[@intCast(@intFromEnum(symbol.section_number) - 1)]; - const section_sym = section.si.get(coff); - - const sym = si.get(coff); - sym.ni = section_sym.ni; - sym.value = .{ .input_offset = symbol.value }; - sym.section_number = section_sym.section_number; + switch (symbol.storage_class) { + .STATIC, .LABEL => |storage_class| switch (symbol.section_number) { + .UNDEFINED, .DEBUG, .ABSOLUTE => { + // TODO: Do we need to do anything with @feat.00? + // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392 + }, + else => |sn| { + // Section symbol + if (storage_class == .STATIC and + symbol.value == 0 and + symbol.type == std.coff.SymType{ + .complex_type = .NULL, + .base_type = .NULL, + } and + symbol.number_of_aux_symbols > 0) + { + if (symbol.number_of_aux_symbols > 1) + return diags.failParse(path, "invalid number of aux symbols for section 0x{x}: {d}", .{ + symbol_ix, + symbol.number_of_aux_symbols, + }); + + var section_def: std.coff.SectionDefinition = undefined; + @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], try r.peek(symbol_size)); + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def); + + // TODO: Extract the COMDAT section info + + if (section_def.number > sections.len) + return diags.failParse( + path, + "section symbol for '{s}' contained an out of bounds section number: 0x{x}", + .{ name, section_def.number }, + ); + + // It's valid for this to not match the symbol's section number (ie. .drectve sets this) + if (section_def.number == 0) + continue; + + const section = §ions[section_def.number - 1]; + if (section_def.number_of_relocations != section.header.number_of_relocations) + return diags.failParse( + path, + "section symbol for '{s}' relocation count did not match section header: {d} vs {d}", + .{ name, section_def.number_of_relocations, section.header.number_of_relocations }, + ); + + if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) + return diags.failParse( + path, + "section symbol for '{s}' line number count did not match section header: {d} vs {d}", + .{ name, section_def.number_of_linenumbers, section.header.number_of_linenumbers }, + ); + + @memset(si_slice, section.si); + } else { + try coff.symbols.ensureUnusedCapacity(gpa, 1); + si_slice[0] = coff.addSymbolAssumeCapacity(); + const sym = si_slice[0].get(coff); + coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); + } + }, + }, + .EXTERNAL => switch (symbol.section_number) { + .ABSOLUTE => return diags.failParse( + path, + "TODO unhandled external absolute symbol: '{s}'", + .{name}, + ), + .DEBUG => return diags.failParse( + path, + "unexpected external symbol in DEBUG section: '{s}'", + .{name}, + ), + else => |sn| { + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); + si_slice[0] = global_gop.value_ptr.*; + + const sym = si_slice[0].get(coff); + if (sn != .UNDEFINED) { + if (global_gop.found_existing and sym.ni != .none) { + // TODO: Need corresponding logic later if we try to make a global already defined by an input + + var err = try diags.addErrorWithNotes(2); + try err.addMsg("multiple definitions of '{s}'", .{name}); + switch (coff.getNode(sym.ni)) { + .input_section => |isi| { + const other_ii = isi.input(coff); + err.addNote("first seen in input '{f}{f}'", .{ + other_ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(other_ii.archiveName(coff)), + }); + }, + .nav, .uav => err.addNote("first seen in module '{s}'", .{ + comp.zcu.?.root_mod.fully_qualified_name, + }), + else => unreachable, + } + err.addNote("defined again in input '{f}'", .{path}); + return error.LinkFailure; + } + + // TODO: Here if we *were* undefined we want to associate this symbol now with this section for + // the flushMoved iteration + + coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); + } else if (!global_gop.found_existing) { + sym.value = .{ .size = symbol.value }; + } + }, + }, + else => {}, + } } if (coff.symbols.items.len > first_si) { @@ -3539,16 +3610,17 @@ fn loadObject( if (reloc.symbol_table_index >= symbols.items.len) return diags.failParse( path, - "relocation {d} in section '{s}' targets invalid symbol index {d}", + "relocation 0x{x} in section '{s}' targets invalid symbol index 0x{x}", .{ reloc_i, section.name.toSlice(coff), reloc.symbol_table_index }, ); + assert(symbols.items[reloc.symbol_table_index] != .null); try coff.addReloc( section.si, reloc.virtual_address - section.header.virtual_address, symbols.items[reloc.symbol_table_index], .pending, - @bitCast(reloc.type), // TODO: Checks on this cast? + @bitCast(reloc.type), ); } } @@ -3602,6 +3674,8 @@ 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 { _ = coff; _ = prog_node; + + log.debug("prelink()", .{}); } pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -3888,6 +3962,126 @@ fn flushImplib( try file_writer.interface.flush(); } +fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const max_notes = 4; + + var undef_indices: std.ArrayListUnmanaged(u32) = .empty; + for (coff.relocs.items, 0..) |reloc, reloc_i| { + const target_sym = reloc.target.get(coff); + switch (target_sym.ni) { + .none => { + assert(target_sym.gmi != .none); + (try undef_indices.addOne(gpa)).* = @intCast(reloc_i); + }, + else => continue, + } + } + + if (undef_indices.items.len == 0) return; + + const undefLessThan = struct { + fn lessThan(ctx: *const Coff, lhs: u32, rhs: u32) bool { + const reloc_l = &ctx.relocs.items[lhs]; + const reloc_r = &ctx.relocs.items[rhs]; + if (reloc_l.target == reloc_r.target) + return @intFromEnum(reloc_l.loc) < @intFromEnum(reloc_r.loc) + else + return @intFromEnum(reloc_l.target) < @intFromEnum(reloc_r.target); + } + }.lessThan; + + std.mem.sortUnstable(u32, undef_indices.items, coff, undefLessThan); + + var start_i: usize = 0; + var num_unique_references: usize = 1; + for (undef_indices.items[0..], 0..) |reloc_i, i| { + const target = coff.relocs.items[undef_indices.items[start_i]].target; + if (target != coff.relocs.items[reloc_i].target or i == undef_indices.items.len - 1) { + defer { + start_i = i; + num_unique_references = 1; + } + + const num_references = i - start_i; + const num_notes = + @min(max_notes, num_unique_references) + + @intFromBool(num_unique_references > max_notes); + + var err = try comp.link_diags.addErrorWithNotes(num_notes); + const target_sym = target.get(coff); + try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.globalName(coff).name.toSlice(coff)}); + + var prev_loc_si: Symbol.Index = .null; + for (undef_indices.items[start_i..][0..num_references]) |reference_i| { + if (err.note_slot == num_notes) break; + + const loc_si = coff.relocs.items[reference_i].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)) { + .input_section => |isi| { + const other_ii = isi.input(coff); + if (loc_sym.gmi == .none) { + // TODO: We could report the name here if we interned it in loadObject + err.addNote("referenced internally by input '{f}{f}'", .{ + other_ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(other_ii.archiveName(coff)), + }); + } else { + err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{ + loc_sym.gmi.globalName(coff).name.toSlice(coff), + other_ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(other_ii.archiveName(coff)), + }); + } + }, + .global => |gmi| err.addNote("referenced by '{s}' in module '{s}'", .{ + gmi.globalName(coff).name.toSlice(coff), + comp.zcu.?.root_mod.fully_qualified_name, + }), + inline .nav, + .uav, + .lazy_code, + .lazy_const_data, + => |val, tag| { + err.addNote("referenced by '{f}'", .{ + format: switch (tag) { + .nav => { + const ip = &comp.zcu.?.intern_pool; + break :format ip.getNav(val.navIndex(coff)).fqn.fmt(ip); + }, + .uav => Value.fromInterned(val.uavValue(coff)).fmtValue(.{ + .zcu = coff.base.comp.zcu.?, + .tid = tid, + }), + inline .lazy_code, .lazy_const_data => Type.fromInterned(val.lazySymbol(coff).ty).fmt(.{ + .zcu = coff.base.comp.zcu.?, + .tid = tid, + }), + else => unreachable, + }, + }); + }, + else => unreachable, + } + } + + if (num_unique_references > max_notes) + err.addNote("referenced {d} more times", .{num_references - max_notes}); + } else if (i != start_i and + coff.relocs.items[undef_indices.items[i - 1]].loc != coff.relocs.items[undef_indices.items[i]].loc) + { + num_unique_references += 1; + } + } + + return error.LinkFailure; +} + pub fn flush( coff: *Coff, arena: std.mem.Allocator, @@ -3897,6 +4091,7 @@ pub fn flush( _ = arena; _ = prog_node; while (try coff.idle(tid)) {} + try coff.reportUndefs(tid); const comp = coff.base.comp; @@ -4132,7 +4327,7 @@ fn idleProgNode( break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{ ii.path(coff).fmtEscapeString(), fmtArchiveNameString(ii.archiveName(coff)), - isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }) catch &name; }, .global => |gmi| gmi.globalName(coff).name.toSlice(coff), @@ -4386,6 +4581,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); + } else { + + // TODO: If no .ni, report symbol not found - or should it be right when it's added as a global if we don't know about it? + } } @@ -4505,6 +4704,10 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { const ii = isi.input(coff); var si = ii.firstSymbol(coff); const last_si = ii.lastSymbol(coff); + + // TODO: This iteration doesn't visit symbols that were added first + // in the range of another section (as undef). + while (@intFromEnum(si) <= @intFromEnum(last_si)) : (si = si.next()) { if (si.get(coff).ni != ni) continue; si.flushMoved(coff); @@ -5065,7 +5268,7 @@ pub fn printNode( try w.print("({f}{f}, {s})", .{ ii.path(coff).fmtEscapeString(), fmtArchiveNameString(ii.archiveName(coff)), - isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }); }, .import_lookup_table, -- 2.54.0 From d8f23c57aaf70f9dcee25620f79c8a6d5512eaae Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 17/94] Coff: Fixup input object relocations - Handle the case of undefined global symbols becoming defined later by another input, and call flushMoved on these via the new input_resolved tracking array - Fixup not calling flushMoved on the input section symbol itself --- src/link/Coff.zig | 139 +++++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 50 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 9c09f15dd22f229fa0578f72a029bb28e7ba6d19..dfa25b7d2bd4c1a776f03cc4cd94352f41cf8a89 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -31,12 +31,8 @@ long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, symbol_table: SymbolTable, -inputs: std.ArrayList(struct { - path: std.Build.Cache.Path, - archive_name: ?[]const u8, - first_si: Symbol.Index, - last_si: Symbol.Index, -}), +inputs: std.ArrayList(Input), +input_resolved: std.ArrayList(Symbol.Index), input_sections: std.ArrayList(struct { ii: Node.InputIndex, si: Symbol.Index, @@ -298,6 +294,10 @@ pub const Node = union(enum) { pub fn lastSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { return coff.inputs.items[@intFromEnum(ii)].last_si; } + + pub fn firstResolvedGlobal(ii: InputIndex, coff: *const Coff) Input.ResolvedIndex { + return coff.inputs.items[@intFromEnum(ii)].first_iri; + } }; pub const InputSectionIndex = enum(u32) { @@ -384,6 +384,18 @@ pub const Node = union(enum) { } }; +pub const Input = struct { + path: std.Build.Cache.Path, + archive_name: ?[]const u8, + first_si: Symbol.Index, + last_si: Symbol.Index, + first_iri: ResolvedIndex, + + const ResolvedIndex = enum(u32) { + _, + }; +}; + pub const Member = struct { kind: Kind, header_ni: MappedFile.Node.Index, @@ -1270,6 +1282,7 @@ fn create( .pending_shrink = false, }, .inputs = .empty, + .input_resolved = .empty, .input_sections = .empty, .input_section_pending_index = 0, .strings = .empty, @@ -1330,6 +1343,7 @@ pub fn deinit(coff: *Coff) void { coff.symbol_table.strings.deinit(gpa); coff.symbol_table.pending.deinit(gpa); coff.inputs.deinit(gpa); + coff.input_resolved.deinit(gpa); coff.input_sections.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); @@ -2253,7 +2267,7 @@ fn getOrPutGlobalSymbol( pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { const gop = try coff.getOrPutGlobalSymbol(opts); if (gop.found_existing) { - // TODO: Need to know if this is an export or extern, add to opts + // TODO: Need to know if this is an export or extern, in order to decide if this is duplicate, add to opts } return gop.value_ptr.*; @@ -3208,6 +3222,7 @@ fn loadObject( .archive_name = if (archive_name) |m| try gpa.dupe(u8, m) else null, .first_si = .null, .last_si = .null, + .first_iri = @enumFromInt(coff.input_resolved.items.len), }; const string_table = string_table: { @@ -3431,9 +3446,15 @@ fn loadObject( var symbols: std.ArrayList(Symbol.Index) = .empty; try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); + var undefs: std.ArrayList(struct { + symbol_i: u32, + name: String, + size: u32, + }) = .empty; + const first_si = coff.symbols.items.len; - var symbol_ix: u32 = 0; - while (symbol_ix < header.number_of_symbols) { + var symbol_i: u32 = 0; + while (symbol_i < header.number_of_symbols) { var symbol: std.coff.Symbol = undefined; @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], try r.take(symbol_size)); if (target_endian != native_endian) @@ -3441,13 +3462,13 @@ fn loadObject( defer { r.toss(symbol.number_of_aux_symbols * symbol_size); - symbol_ix += symbol.number_of_aux_symbols + 1; + symbol_i += symbol.number_of_aux_symbols + 1; } const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], target_endian); if (index >= string_table.len) - return diags.failParse(path, "bad string offset for symbol 0x{x}", .{symbol_ix}); + return diags.failParse(path, "bad string offset for symbol 0x{x}", .{symbol_i}); break :name string_table[index..]; } else &symbol.name, 0); @@ -3486,7 +3507,7 @@ fn loadObject( { if (symbol.number_of_aux_symbols > 1) return diags.failParse(path, "invalid number of aux symbols for section 0x{x}: {d}", .{ - symbol_ix, + symbol_i, symbol.number_of_aux_symbols, }); @@ -3533,6 +3554,13 @@ fn loadObject( }, }, .EXTERNAL => switch (symbol.section_number) { + .UNDEFINED => { + (try undefs.addOne(gpa)).* = .{ + .symbol_i = symbol_i, + .name = try coff.getOrPutString(name), + .size = symbol.value, + }; + }, .ABSOLUTE => return diags.failParse( path, "TODO unhandled external absolute symbol: '{s}'", @@ -3546,38 +3574,36 @@ fn loadObject( else => |sn| { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); si_slice[0] = global_gop.value_ptr.*; - const sym = si_slice[0].get(coff); - if (sn != .UNDEFINED) { - if (global_gop.found_existing and sym.ni != .none) { - // TODO: Need corresponding logic later if we try to make a global already defined by an input - - var err = try diags.addErrorWithNotes(2); - try err.addMsg("multiple definitions of '{s}'", .{name}); - switch (coff.getNode(sym.ni)) { - .input_section => |isi| { - const other_ii = isi.input(coff); - err.addNote("first seen in input '{f}{f}'", .{ - other_ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(other_ii.archiveName(coff)), - }); - }, - .nav, .uav => err.addNote("first seen in module '{s}'", .{ - comp.zcu.?.root_mod.fully_qualified_name, - }), - else => unreachable, - } - err.addNote("defined again in input '{f}'", .{path}); - return error.LinkFailure; + if (global_gop.found_existing and sym.ni != .none) { + // TODO: Need corresponding logic later if we try to make a global already defined by an input + var err = try diags.addErrorWithNotes(2); + try err.addMsg("multiple definitions of '{s}'", .{name}); + switch (coff.getNode(sym.ni)) { + .input_section => |isi| { + const other_ii = isi.input(coff); + err.addNote("first seen in input '{f}{f}'", .{ + other_ii.path(coff).fmtEscapeString(), + fmtArchiveNameString(other_ii.archiveName(coff)), + }); + }, + .nav, .uav => err.addNote("first seen in module '{s}'", .{ + comp.zcu.?.root_mod.fully_qualified_name, + }), + else => unreachable, } + err.addNote("defined again in input '{f}'", .{path}); + return error.LinkFailure; + } - // TODO: Here if we *were* undefined we want to associate this symbol now with this section for - // the flushMoved iteration - - coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); - } else if (!global_gop.found_existing) { - sym.value = .{ .size = symbol.value }; + if (global_gop.found_existing) { + // `input_resolved` allows visting this symbol in this input section's flushMoved, + // as the previously undefined global created earlier will not be in our + // contiguous first / last range. + (try coff.input_resolved.addOne(gpa)).* = si_slice[0]; } + + coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); }, }, else => {}, @@ -3589,6 +3615,18 @@ fn loadObject( input.last_si = @enumFromInt(coff.symbols.items.len - 1); } + // These are added after all the defined symbols are created so they are not part of the + // input's symbol range, which should only contain symbols that are actually located in this input. + for (undefs.items) |undef| { + // TODO: Avoid redundant hashing by having name be a union on String / []const u8 + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = undef.name.toSlice(coff) }); + symbols.items[undef.symbol_i] = global_gop.value_ptr.*; + if (!global_gop.found_existing) { + const sym = symbols.items[undef.symbol_i].get(coff); + sym.value = .{ .size = undef.size }; + } + } + const relocation_size = std.coff.Relocation.sizeOf(); for (sections) |section| { if (section.si == .null) continue; @@ -4581,10 +4619,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); - } else { - - // TODO: If no .ni, report symbol not found - or should it be right when it's added as a global if we don't know about it? - } } @@ -4702,14 +4736,19 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { }, .input_section => |isi| { const ii = isi.input(coff); - var si = ii.firstSymbol(coff); - const last_si = ii.lastSymbol(coff); + isi.symbol(coff).flushMoved(coff); - // TODO: This iteration doesn't visit symbols that were added first - // in the range of another section (as undef). + { + var si = ii.firstSymbol(coff); + const last_si = ii.lastSymbol(coff); + while (@intFromEnum(si) <= @intFromEnum(last_si)) : (si = si.next()) { + if (si.get(coff).ni != ni) continue; + si.flushMoved(coff); + } + } - while (@intFromEnum(si) <= @intFromEnum(last_si)) : (si = si.next()) { - if (si.get(coff).ni != ni) continue; + for (coff.input_resolved.items[@intFromEnum(ii.firstResolvedGlobal(coff))..]) |si| { + if (si.get(coff).ni != ni) break; si.flushMoved(coff); } }, -- 2.54.0 From a4b1a3a0b367912720122791f02289ea4a69cb84 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 18/94] Coff: more work on inputs - Fixup undefined symbol notes when there was a single one - Fixup undefined symbols being added to the linker members - Fix string table alignment causing resizes to add extra bytes (which were not correctly zeroed out) --- src/link/Coff.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index dfa25b7d2bd4c1a776f03cc4cd94352f41cf8a89..7915b65cf06c146faaf34c0487213ac64e74109a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1707,7 +1707,6 @@ fn initHeaders( coff.nodes.appendAssumeCapacity(.symbol_table); coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ - .alignment = .@"2", .size = @sizeOf(u32), .fixed = true, .resized = true, @@ -2114,7 +2113,7 @@ pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) *align(2) st return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); } -pub fn symbolTableStringLenPtr(coff: *Coff) *align(2) u32 { +pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 { return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)])); } @@ -3483,7 +3482,7 @@ fn loadObject( }); if (is_archive) { - if (symbol.storage_class == .EXTERNAL) + if (symbol.storage_class == .EXTERNAL and symbol.section_number != .UNDEFINED) try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); continue; @@ -4042,7 +4041,6 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { num_unique_references = 1; } - const num_references = i - start_i; const num_notes = @min(max_notes, num_unique_references) + @intFromBool(num_unique_references > max_notes); @@ -4052,7 +4050,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.globalName(coff).name.toSlice(coff)}); var prev_loc_si: Symbol.Index = .null; - for (undef_indices.items[start_i..][0..num_references]) |reference_i| { + for (undef_indices.items[start_i..][0..@max(1, i - start_i)]) |reference_i| { if (err.note_slot == num_notes) break; const loc_si = coff.relocs.items[reference_i].loc; @@ -4109,7 +4107,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { } if (num_unique_references > max_notes) - err.addNote("referenced {d} more times", .{num_references - max_notes}); + err.addNote("referenced {d} more times", .{num_unique_references - max_notes}); } else if (i != start_i and coff.relocs.items[undef_indices.items[i - 1]].loc != coff.relocs.items[undef_indices.items[i]].loc) { @@ -4129,7 +4127,9 @@ pub fn flush( _ = arena; _ = prog_node; while (try coff.idle(tid)) {} - try coff.reportUndefs(tid); + + if (coff.isImage()) + try coff.reportUndefs(tid); const comp = coff.base.comp; -- 2.54.0 From fc81358b75f155aa16ae145de06a30c6ccd30e67 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 19/94] Coff: Progress on loading archives - Remove unnecessary deferring of registering undef globals - Start parsing linker members --- src/link/Coff.zig | 180 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 142 insertions(+), 38 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7915b65cf06c146faaf34c0487213ac64e74109a..26151c2934a9f605470cf6109757fc285b2401c1 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -76,6 +76,9 @@ pub const default_size_of_stack_commit: u32 = 0x1000; pub const default_size_of_heap_reserve: u32 = 0x100000; pub const default_size_of_heap_commit: u32 = 0x1000; +pub const archive_signature = "!\n"; +pub const archive_end_of_header = "`\n"; + /// This is the start of a Portable Executable (PE) file. /// It starts with a MS-DOS header followed by a MS-DOS stub program. /// This data does not change so we include it as follows in all binaries. @@ -494,7 +497,7 @@ pub const Member = struct { member.content_ni.location(&coff.mf).resolve(&coff.mf)[1], ); - @memcpy(&header.end_of_header, "`\n"); + @memcpy(&header.end_of_header, archive_end_of_header); } pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void { @@ -507,6 +510,17 @@ pub const Member = struct { .fill = ' ', }); } + + pub fn loadHeaderDecimalStr(field_ptr: anytype, value: u64) void { + const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array; + assert(array_info.child == u8); + assert(value < comptime try std.math.powi(u64, 10, array_info.len)); + _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{ + .width = array_info.len, + .alignment = .left, + .fill = ' ', + }); + } }; pub const LongNamesTable = struct { @@ -1450,7 +1464,6 @@ fn initHeaders( coff.nodes.appendAssumeCapacity(.header); const pe_signature = "PE\x00\x00"; - const archive_signature = "!\n"; const signature_ni = Node.known.signature; assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{ @@ -2686,10 +2699,6 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void { if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; si.applyLocationRelocs(coff); - - // TODO: Problem is that if the sym is first seen as undef, it's si is in the range of the section - // that wants that symbol. but when the section that contains it is moved, the iteration doesn't see - // that symbol. } fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { @@ -3445,12 +3454,6 @@ fn loadObject( var symbols: std.ArrayList(Symbol.Index) = .empty; try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); - var undefs: std.ArrayList(struct { - symbol_i: u32, - name: String, - size: u32, - }) = .empty; - const first_si = coff.symbols.items.len; var symbol_i: u32 = 0; while (symbol_i < header.number_of_symbols) { @@ -3554,11 +3557,12 @@ fn loadObject( }, .EXTERNAL => switch (symbol.section_number) { .UNDEFINED => { - (try undefs.addOne(gpa)).* = .{ - .symbol_i = symbol_i, - .name = try coff.getOrPutString(name), - .size = symbol.value, - }; + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); + si_slice[0] = global_gop.value_ptr.*; + if (!global_gop.found_existing) { + const sym = si_slice[0].get(coff); + sym.value = .{ .size = symbol.value }; + } }, .ABSOLUTE => return diags.failParse( path, @@ -3614,18 +3618,6 @@ fn loadObject( input.last_si = @enumFromInt(coff.symbols.items.len - 1); } - // These are added after all the defined symbols are created so they are not part of the - // input's symbol range, which should only contain symbols that are actually located in this input. - for (undefs.items) |undef| { - // TODO: Avoid redundant hashing by having name be a union on String / []const u8 - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = undef.name.toSlice(coff) }); - symbols.items[undef.symbol_i] = global_gop.value_ptr.*; - if (!global_gop.found_existing) { - const sym = symbols.items[undef.symbol_i].get(coff); - sym.value = .{ .size = undef.size }; - } - } - const relocation_size = std.coff.Relocation.sizeOf(); for (sections) |section| { if (section.si == .null) continue; @@ -3663,23 +3655,135 @@ fn loadObject( } } +fn parseArchiveHeader( + header: *const std.coff.ArchiveMemberHeader, + opt_longnames: ?[]const u8, +) !struct { + name: []const u8, + size: u34, +} { + const trim = std.mem.trimEnd(u8, &header.name, &.{' '}); + + if (trim.len == 0) return error.BadName; + const name = if (trim[0] == '/') name: { + if (trim.len == 1 or + trim.len == 2 and trim[1] == '/') + break :name trim; + + const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch + return error.BadName; + + if (opt_longnames) |longnames| { + if (offset >= longnames.len) return error.BadName; + break :name std.mem.sliceTo(longnames[offset..], 0); + } else return error.NoLongNames; + } else if (trim[trim.len - 1] == '/') + trim[0 .. trim.len - 1] + else + return error.BadName; + + const size = std.fmt.parseUnsigned(u34, std.mem.trimEnd(u8, &header.size, &.{' '}), 10) catch + return error.BadSize; + + if (!std.mem.eql(u8, &header.end_of_header, archive_end_of_header)) + return error.BadEndOfHeader; + + return .{ + .name = name, + .size = size, + }; +} + fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { const comp = coff.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; const r = &fr.interface; + const target_endian = coff.targetEndian(); log.debug("loadArchive({f})", .{path.fmtEscapeString()}); - // TODO: Skip over 1st linker member - // TODO: Build index of symbols -> members from 2nd linker member - // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images) - // TODO: Lazily call loadObject whenever a symbol is need from one of the members. - // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)? - - _ = gpa; - _ = diags; - _ = r; + const signature = try r.take(archive_signature.len); + if (!std.mem.eql(u8, signature, archive_signature)) + return diags.failParse(path, "bad signature", .{}); + + var opt_expected_kind: ?Member.Kind = .first_linker; + var opt_longnames: ?[]const u8 = null; + defer if (opt_longnames) |l| gpa.free(l); + + var pos = fr.logicalPos(); + const size = try fr.getSize(); + while (pos < size) : (pos = fr.logicalPos()) { + const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian); + const res = parseArchiveHeader(&header, opt_longnames) catch |err| switch (err) { + error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}), + error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}), + error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}), + error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}), + }; + + if (pos + res.size > size) + return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name }); + + log.debug("loadArchiveMember({s})", .{res.name}); + + if (opt_expected_kind) |expected_kind| expected: switch (expected_kind) { + .first_linker => { + if (!std.mem.eql(u8, res.name, "/")) + return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name}); + + try fr.seekTo(fr.logicalPos() + res.size); + opt_expected_kind = .second_linker; + continue; + }, + .second_linker => { + if (!std.mem.eql(u8, res.name, "/")) + return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name}); + + // TODO: Parse this! + // TODO: Build index of symbols -> members from 2nd linker member + // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images) + // TODO: Lazily call loadObject whenever a symbol is need from one of the members. + // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)? + try r.discardAll(res.size); + + opt_expected_kind = .longnames; + continue; + }, + .longnames => { + defer opt_expected_kind = null; + + // This member is optional + if (!std.mem.eql(u8, res.name, "//")) break :expected; + opt_longnames = try r.readAlloc(gpa, res.size); + continue; + }, + else => unreachable, + }; + + const member_sig = try r.peek(4); + const machine = std.mem.readInt(u16, member_sig[0..2], target_endian); + const sig = std.mem.readInt(u16, member_sig[2..4], target_endian); + if (machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff) { + const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian); + + // TODO: Validate import table header fields + _ = import_header; + } else { + const coff_header = try r.peekStruct(std.coff.Header, target_endian); + + // TODO: Validate COFF header fields + _ = coff_header; + } + + try fr.seekTo(fr.logicalPos() + res.size); + } + + if (opt_expected_kind) |expected_kind| switch (expected_kind) { + .first_linker => return diags.failParse(path, "missing first linker member", .{}), + .second_linker => return diags.failParse(path, "missing second linker member", .{}), + else => {}, + }; } fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { -- 2.54.0 From cef6ea05d982589d29bc312b43bde1432ea78623 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 20/94] Coff: Loading input archives - Build a lookup table for symbols from each loaded archive --- src/link/Coff.zig | 282 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 239 insertions(+), 43 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 26151c2934a9f605470cf6109757fc285b2401c1..83a9dd4a76b5bf703a78db3fc1f491f0debfa992 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -31,6 +31,13 @@ long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, symbol_table: SymbolTable, +input_archives: std.ArrayList(InputArchive), +input_archive_members: std.ArrayList(InputArchive.Member), +input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol), +input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct { + first: InputArchive.Member.Symbol.Index, + last: InputArchive.Member.Symbol.Index, +}), inputs: std.ArrayList(Input), input_resolved: std.ArrayList(Symbol.Index), input_sections: std.ArrayList(struct { @@ -47,7 +54,6 @@ strings: std.HashMapUnmanaged( ), string_bytes: std.ArrayList(u8), section_table: std.AutoArrayHashMapUnmanaged(String, Section), -tls_si: Symbol.Index, pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), symbols: std.ArrayList(Symbol), @@ -387,6 +393,36 @@ pub const Node = union(enum) { } }; +pub const InputArchive = struct { + path: std.Build.Cache.Path, + + const Index = enum(u32) { + _, + }; + + pub const Member = struct { + iai: InputArchive.Index, + name: String, + // This range includes the member header + file_location: MappedFile.Node.FileLocation, + is_import: bool, + // TODO: Field indicating we loaded it already (ii) + const Index = enum(u32) { + _, + }; + + pub const Symbol = struct { + iami: InputArchive.Member.Index, + // Set to its own index to indicate its the last in the list + next: InputArchive.Member.Symbol.Index, + + const Index = enum(u32) { + _, + }; + }; + }; +}; + pub const Input = struct { path: std.Build.Cache.Path, archive_name: ?[]const u8, @@ -771,6 +807,7 @@ pub const Symbol = struct { /// Relocations targeting this symbol target_relocs: Reloc.Index, section_number: SectionNumber, + /// Only used when outputting objects sti: SymbolTable.Index, gmi: Node.GlobalMapIndex, unused0: u16 = 0, @@ -1295,6 +1332,10 @@ fn create( .pending = .empty, .pending_shrink = false, }, + .input_archives = .empty, + .input_archive_members = .empty, + .input_archive_symbols = .empty, + .input_archive_symbol_indices = .empty, .inputs = .empty, .input_resolved = .empty, .input_sections = .empty, @@ -1302,7 +1343,6 @@ fn create( .strings = .empty, .string_bytes = .empty, .section_table = .empty, - .tls_si = .null, .pseudo_section_table = .empty, .object_section_table = .empty, .symbols = .empty, @@ -1356,6 +1396,10 @@ pub fn deinit(coff: *Coff) void { coff.export_table.entries.deinit(gpa); coff.symbol_table.strings.deinit(gpa); coff.symbol_table.pending.deinit(gpa); + coff.input_archives.deinit(gpa); + coff.input_archive_members.deinit(gpa); + coff.input_archive_symbols.deinit(gpa); + coff.input_archive_symbol_indices.deinit(gpa); coff.inputs.deinit(gpa); coff.input_resolved.deinit(gpa); coff.input_sections.deinit(gpa); @@ -1841,7 +1885,7 @@ fn initHeaders( if (comp.config.any_non_single_threaded) { if (!is_image) - coff.tls_si = try coff.addSection(.@".tls$", .{ + _ = try coff.addSection(.@".tls$", .{ .CNT_INITIALIZED_DATA = true, .MEM_READ = true, .MEM_WRITE = true, @@ -3655,13 +3699,29 @@ fn loadObject( } } -fn parseArchiveHeader( - header: *const std.coff.ArchiveMemberHeader, - opt_longnames: ?[]const u8, -) !struct { +const ArchiveMemberHeader = struct { name: []const u8, size: u34, -} { +}; + +fn parseArchiveMemberHeader( + diags: *link.Diags, + path: std.Build.Cache.Path, + header: *const std.coff.ArchiveMemberHeader, + opt_longnames: ?[]const u8, +) !ArchiveMemberHeader { + return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) { + error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}), + error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}), + error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}), + error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}), + }; +} + +fn parseArchiveMemberHeaderInner( + header: *const std.coff.ArchiveMemberHeader, + opt_longnames: ?[]const u8, +) !ArchiveMemberHeader { const trim = std.mem.trimEnd(u8, &header.name, &.{' '}); if (trim.len == 0) return error.BadName; @@ -3711,23 +3771,58 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo var opt_longnames: ?[]const u8 = null; defer if (opt_longnames) |l| gpa.free(l); + var members: std.ArrayList(struct { + offset: u32, + iami: ?InputArchive.Member.Index, + }) = .empty; + var symbol_member_indices: std.ArrayList(u32) = .empty; + + const iai: InputArchive.Index = @enumFromInt(coff.input_archives.items.len); + (try coff.input_archives.addOne(gpa)).* = .{ + .path = path, + }; + + const first_iami = coff.input_archive_members.items.len; + const first_iamsi = coff.input_archive_symbols.items.len; + const first_symbol_indices_index = coff.input_archive_symbol_indices.count(); + + errdefer { + for (coff.input_archive_symbol_indices.values()) |*v| { + if (@intFromEnum(v.last) < first_iamsi) continue; + if (@intFromEnum(v.first) >= first_iamsi) continue; + + var iter = v.first; + v.last = while (iter != v.last) { + const sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; + if (@intFromEnum(sym.next) >= first_iamsi) { + sym.next = iter; + break iter; + } + + iter = sym.next; + } else unreachable; + } + + // New entries in this map will only have pointed to iamsi we also just added + coff.input_archive_symbol_indices.shrinkRetainingCapacity(first_symbol_indices_index); + coff.input_archive_symbols.shrinkRetainingCapacity(first_iamsi); + coff.input_archive_members.shrinkRetainingCapacity(first_iami); + _ = coff.input_archives.pop(); + } + var pos = fr.logicalPos(); const size = try fr.getSize(); while (pos < size) : (pos = fr.logicalPos()) { const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian); - const res = parseArchiveHeader(&header, opt_longnames) catch |err| switch (err) { - error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}), - error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}), - error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}), - error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}), - }; + const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames); - if (pos + res.size > size) + const member_end = fr.logicalPos() + res.size; + if (member_end > size) return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name }); log.debug("loadArchiveMember({s})", .{res.name}); - if (opt_expected_kind) |expected_kind| expected: switch (expected_kind) { + if (opt_expected_kind) |expected_kind| switch (expected_kind) { .first_linker => { if (!std.mem.eql(u8, res.name, "/")) return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name}); @@ -3740,50 +3835,142 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo if (!std.mem.eql(u8, res.name, "/")) return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name}); - // TODO: Parse this! - // TODO: Build index of symbols -> members from 2nd linker member - // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images) - // TODO: Lazily call loadObject whenever a symbol is need from one of the members. - // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)? - try r.discardAll(res.size); + const num_members = try r.takeInt(u32, target_endian); + pos = fr.logicalPos(); + if (pos + num_members * 4 > member_end) + return diags.failParse(path, "invalid member count 0x{x} in second linker member", .{num_members}); + try members.ensureTotalCapacity(gpa, num_members); + for (0..num_members) |_| + members.addOneAssumeCapacity().* = .{ + .offset = try r.takeInt(u32, target_endian), + .iami = null, + }; + + const num_symbols = try r.takeInt(u32, target_endian); + pos = fr.logicalPos(); + if (pos + num_symbols * 2 > member_end) + return diags.failParse(path, "invalid symbol count 0x{x} in second linker member", .{num_symbols}); + + try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); + for (0..num_symbols) |_| + symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, target_endian)) - 1; + + pos = fr.logicalPos(); + try coff.ensureManyUnusedStringCapacity(num_symbols, member_end - pos); + try coff.input_archive_members.ensureUnusedCapacity(gpa, num_members); + try coff.input_archive_symbols.ensureUnusedCapacity(gpa, num_symbols); + try coff.input_archive_symbol_indices.ensureUnusedCapacity(gpa, num_symbols); + + var symbol_i: u32 = 0; + while (pos < member_end and symbol_i < num_symbols) : ({ + pos = fr.logicalPos(); + symbol_i += 1; + }) { + const name = if (r.takeDelimiter(0) catch |err| switch (err) { + error.StreamTooLong => null, + else => |e| return e, + }) |n| n else return diags.failParse(path, "unterminated string found in second linker member", .{}); + + const string = coff.getOrPutStringAssumeCapacity(name); + const iamsi: InputArchive.Member.Symbol.Index = @enumFromInt(coff.input_archive_symbols.items.len); + const symbol_gop = coff.input_archive_symbol_indices.getOrPutAssumeCapacity(string); + if (!symbol_gop.found_existing) { + symbol_gop.value_ptr.* = .{ + .first = iamsi, + .last = iamsi, + }; + } else { + coff.input_archive_symbols.items[@intFromEnum(symbol_gop.value_ptr.last)].next = iamsi; + symbol_gop.value_ptr.last = iamsi; + } + + const iami = members.items[symbol_member_indices.items[symbol_i]].iami orelse iami: { + const iami: InputArchive.Member.Index = @enumFromInt(coff.input_archive_members.items.len); + const member_offset = members.items[symbol_member_indices.items[symbol_i]].offset; + coff.input_archive_members.addOneAssumeCapacity().* = .{ + .iai = iai, + .name = undefined, + .is_import = undefined, + .file_location = .{ + .offset = member_offset, + .size = undefined, + }, + }; + + members.items[symbol_member_indices.items[symbol_i]].iami = iami; + break :iami iami; + }; + + log.debug("loadArchiveMemberSymbol({s}) = ({d}, {d}, {d})", .{ name, iai, iami, iamsi }); + + coff.input_archive_symbols.addOneAssumeCapacity().* = .{ + .iami = iami, + .next = iamsi, + }; + } + + if (symbol_i != num_symbols) + return diags.failParse( + path, + " expected {d} entries in second linker member string table, but found {d}", + .{ num_symbols, symbol_i }, + ); + + try fr.seekTo(member_end); opt_expected_kind = .longnames; continue; }, .longnames => { - defer opt_expected_kind = null; - // This member is optional - if (!std.mem.eql(u8, res.name, "//")) break :expected; - opt_longnames = try r.readAlloc(gpa, res.size); - continue; + if (std.mem.eql(u8, res.name, "//")) + opt_longnames = try r.readAlloc(gpa, res.size); + + opt_expected_kind = null; + break; }, else => unreachable, }; + } + + if (opt_expected_kind) |expected_kind| switch (expected_kind) { + .first_linker => return diags.failParse(path, "missing first linker member", .{}), + .second_linker => return diags.failParse(path, "missing second linker member", .{}), + else => {}, + }; + + // Validate / read names and sizes of all the referenced members + for (coff.input_archive_members.items[first_iami..]) |*member| { + try fr.seekTo(member.file_location.offset); + + const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian); + const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames); + + try coff.ensureUnusedStringCapacity(res.name.len); + member.name = coff.getOrPutStringAssumeCapacity(res.name); const member_sig = try r.peek(4); const machine = std.mem.readInt(u16, member_sig[0..2], target_endian); const sig = std.mem.readInt(u16, member_sig[2..4], target_endian); - if (machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff) { + member.is_import = machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff; + member.file_location.size = res.size; + + log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{ + res.name, + member.file_location.offset, + member.file_location.size, + }); + + if (member.is_import) { const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian); - // TODO: Validate import table header fields - _ = import_header; - } else { - const coff_header = try r.peekStruct(std.coff.Header, target_endian); - - // TODO: Validate COFF header fields - _ = coff_header; + // TODO: Use this result in flushGlobal + return diags.failParse(path, "TODO implement parsing import headers: {t} {t}", .{ + import_header.types.type, + import_header.types.name_type, + }); } - - try fr.seekTo(fr.logicalPos() + res.size); } - - if (opt_expected_kind) |expected_kind| switch (expected_kind) { - .first_linker => return diags.failParse(path, "missing first linker member", .{}), - .second_linker => return diags.failParse(path, "missing second linker member", .{}), - else => {}, - }; } fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { @@ -4723,6 +4910,15 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); + } else { + + // TODO: Check if not defined, and if in an input member + // TODO: If so, queue a loadObject for that member + // TODO: Return a value indicating to retry this flushGlobal + // TODO: The loadObject idle task should be before the flushGlobal idle task + // + // TODO: Check if we can get flushGlobal before prelink, that would cause a problem + } } -- 2.54.0 From 3ee12f3a2aeab1aff5852466cf0c654222e034cc Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 21/94] Coff: linking against archives is now functional - Fixup not aligning section header start when reading archives - Load archive members on-deman as symbols from them are referenced - Sort symbols by section before building inputs_resolved so that ranges are contiguous --- src/link/Coff.zig | 263 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 182 insertions(+), 81 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 83a9dd4a76b5bf703a78db3fc1f491f0debfa992..ec4c301057fabda514f2e43b959b28230b5551ee 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,13 +38,10 @@ input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct { first: InputArchive.Member.Symbol.Index, last: InputArchive.Member.Symbol.Index, }), +pending_input: ?InputArchive.Member.Index, inputs: std.ArrayList(Input), input_resolved: std.ArrayList(Symbol.Index), -input_sections: std.ArrayList(struct { - ii: Node.InputIndex, - si: Symbol.Index, - file_location: MappedFile.Node.FileLocation, -}), +input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, strings: std.HashMapUnmanaged( u32, @@ -203,7 +200,7 @@ pub const Node = union(enum) { pseudo_section: PseudoSectionMapIndex, object_section: ObjectSectionMapIndex, - input_section: InputSectionIndex, + input_section: InputSection.Index, global: GlobalMapIndex, nav: NavMapIndex, uav: UavMapIndex, @@ -292,16 +289,16 @@ pub const Node = union(enum) { return coff.inputs.items[@intFromEnum(ii)].path; } - pub fn archiveName(ii: InputIndex, coff: *const Coff) ?[]const u8 { - return coff.inputs.items[@intFromEnum(ii)].archive_name; + pub fn memberName(ii: InputIndex, coff: *const Coff) ?[]const u8 { + return coff.inputs.items[@intFromEnum(ii)].member_name; } pub fn firstSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { return coff.inputs.items[@intFromEnum(ii)].first_si; } - pub fn lastSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { - return coff.inputs.items[@intFromEnum(ii)].last_si; + pub fn endSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { + return coff.inputs.items[@intFromEnum(ii)].end_si; } pub fn firstResolvedGlobal(ii: InputIndex, coff: *const Coff) Input.ResolvedIndex { @@ -309,24 +306,39 @@ pub const Node = union(enum) { } }; - pub const InputSectionIndex = enum(u32) { - _, + const InputSection = struct { + ii: Node.InputIndex, + si: Symbol.Index, + file_location: MappedFile.Node.FileLocation, + first_iri: Node.InputSection.ResolvedIndex, - pub fn input(isi: InputSectionIndex, coff: *const Coff) InputIndex { - return coff.input_sections.items[@intFromEnum(isi)].ii; - } + pub const Index = enum(u32) { + _, - pub fn fileLocation(isi: InputSectionIndex, coff: *const Coff) MappedFile.Node.FileLocation { - return coff.input_sections.items[@intFromEnum(isi)].file_location; - } + pub fn inputSection(isi: Index, coff: *const Coff) *InputSection { + return &coff.input_sections.items[@intFromEnum(isi)]; + } - pub fn symbol(isi: InputSectionIndex, coff: *const Coff) Symbol.Index { - return coff.input_sections.items[@intFromEnum(isi)].si; - } + pub fn input(isi: Index, coff: *const Coff) InputIndex { + return coff.input_sections.items[@intFromEnum(isi)].ii; + } - pub fn lastSymbol(isi: InputSectionIndex, coff: *const Coff) Symbol.Index { - return coff.input_sections.items[@intFromEnum(isi)].last_si; - } + pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation { + return coff.input_sections.items[@intFromEnum(isi)].file_location; + } + + pub fn symbol(isi: Index, coff: *const Coff) Symbol.Index { + return coff.input_sections.items[@intFromEnum(isi)].si; + } + + pub fn firstResolvedSymbol(isi: Index, coff: *const Coff) ResolvedIndex { + return coff.input_sections.items[@intFromEnum(isi)].first_iri; + } + }; + + const ResolvedIndex = enum(u32) { + _, + }; }; pub const LazyMapRef = struct { @@ -398,6 +410,10 @@ pub const InputArchive = struct { const Index = enum(u32) { _, + + pub fn path(iai: InputArchive.Index, coff: *Coff) std.Build.Cache.Path { + return coff.input_archives.items[@intFromEnum(iai)].path; + } }; pub const Member = struct { @@ -405,10 +421,17 @@ pub const InputArchive = struct { name: String, // This range includes the member header file_location: MappedFile.Node.FileLocation, - is_import: bool, - // TODO: Field indicating we loaded it already (ii) + flags: packed struct { + is_import: bool, + is_loaded: bool, + }, + const Index = enum(u32) { _, + + pub fn member(iami: InputArchive.Member.Index, coff: *Coff) *InputArchive.Member { + return &coff.input_archive_members.items[@intFromEnum(iami)]; + } }; pub const Symbol = struct { @@ -425,14 +448,9 @@ pub const InputArchive = struct { pub const Input = struct { path: std.Build.Cache.Path, - archive_name: ?[]const u8, + member_name: ?[]const u8, first_si: Symbol.Index, - last_si: Symbol.Index, - first_iri: ResolvedIndex, - - const ResolvedIndex = enum(u32) { - _, - }; + end_si: Symbol.Index, }; pub const Member = struct { @@ -1336,6 +1354,7 @@ fn create( .input_archive_members = .empty, .input_archive_symbols = .empty, .input_archive_symbol_indices = .empty, + .pending_input = null, .inputs = .empty, .input_resolved = .empty, .input_sections = .empty, @@ -2315,6 +2334,8 @@ fn getOrPutGlobalSymbol( si.get(coff).gmi = .wrap(@intCast(sym_gop.index)); sym_gop.value_ptr.* = si; coff.synth_prog_node.increaseEstimatedTotalItems(1); + + log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si }); } return sym_gop; @@ -2724,7 +2745,28 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void log.debug("updateSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); } -fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void { +fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void { + const member = iami.member(coff); + if (member.file_location.size == 0) return; + assert(!member.flags.is_loaded); + defer member.flags.is_loaded = true; + const comp = coff.base.comp; + const io = comp.io; + const path = member.iai.path(coff); + const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); + defer file.close(io); + var buffer: [4096]u8 = undefined; + var fr = file.reader(io, &buffer); + const offset = member.file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader); + try fr.seekTo(offset); + log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) }); + try coff.loadObject(path, member.name.toSlice(coff), &fr, .{ + .offset = offset, + .size = member.file_location.size, + }); +} + +fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { const file_loc = isi.fileLocation(coff); if (file_loc.size == 0) return; const comp = coff.base.comp; @@ -2740,6 +2782,11 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void { const si = isi.symbol(coff); si.node(coff).writer(&coff.mf, gpa, &nw); defer nw.deinit(); + log.debug("flushInputSection({f}{f}, {s})", .{ + path, + fmtMemberNameString(ii.memberName(coff)), + isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + }); if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; si.applyLocationRelocs(coff); @@ -3180,12 +3227,12 @@ pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError || } } -fn fmtArchiveNameString(archiveName: ?[]const u8) std.fmt.Alt(?[]const u8, archiveNameStringEscape) { - return .{ .data = archiveName }; +fn fmtMemberNameString(memberName: ?[]const u8) std.fmt.Alt(?[]const u8, memberNameStringEscape) { + return .{ .data = memberName }; } -fn archiveNameStringEscape(archiveName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { - try w.print("({f})", .{std.zig.fmtString(archiveName orelse return)}); +fn memberNameStringEscape(memberName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { + try w.print("({f})", .{std.zig.fmtString(memberName orelse return)}); } fn inputSectionHeaderNameSlice( @@ -3214,7 +3261,7 @@ fn inputSectionHeaderNameSlice( fn loadObject( coff: *Coff, path: std.Build.Cache.Path, - archive_name: ?[]const u8, + member_name: ?[]const u8, fr: *Io.File.Reader, fl: MappedFile.Node.FileLocation, ) !void { @@ -3227,7 +3274,7 @@ fn loadObject( const is_archive = coff.isArchive(); assert(!coff.isObj()); - log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtArchiveNameString(archive_name) }); + log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) }); const header = try r.peekStruct(std.coff.Header, coff.targetEndian()); if (header.machine != target.toCoffMachine()) @@ -3271,10 +3318,9 @@ fn loadObject( const input = coff.inputs.addOneAssumeCapacity(); input.* = .{ .path = path, - .archive_name = if (archive_name) |m| try gpa.dupe(u8, m) else null, - .first_si = .null, - .last_si = .null, - .first_iri = @enumFromInt(coff.input_resolved.items.len), + .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null, + .first_si = @enumFromInt(coff.symbols.items.len), + .end_si = @enumFromInt(coff.symbols.items.len), }; const string_table = string_table: { @@ -3455,6 +3501,7 @@ fn loadObject( .offset = fl.offset + section.header.pointer_to_raw_data, .size = section.header.size_of_raw_data, }, + .first_iri = @enumFromInt(coff.input_resolved.items.len), }; log.debug("loadInputSection({s}) = {d}@{d}", .{ section.name.toSlice(coff), section.si, sym.section_number }); @@ -3497,8 +3544,11 @@ fn loadObject( var symbols: std.ArrayList(Symbol.Index) = .empty; try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); + var num_resolved: u32 = 0; + + input.first_si = @enumFromInt(coff.symbols.items.len); + defer input.end_si = @enumFromInt(coff.symbols.items.len); - const first_si = coff.symbols.items.len; var symbol_i: u32 = 0; while (symbol_i < header.number_of_symbols) { var symbol: std.coff.Symbol = undefined; @@ -3619,6 +3669,7 @@ fn loadObject( .{name}, ), else => |sn| { + // TODO: Should this use archive name as lib_name as well? const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); si_slice[0] = global_gop.value_ptr.*; const sym = si_slice[0].get(coff); @@ -3631,7 +3682,7 @@ fn loadObject( const other_ii = isi.input(coff); err.addNote("first seen in input '{f}{f}'", .{ other_ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(other_ii.archiveName(coff)), + fmtMemberNameString(other_ii.memberName(coff)), }); }, .nav, .uav => err.addNote("first seen in module '{s}'", .{ @@ -3643,12 +3694,8 @@ fn loadObject( return error.LinkFailure; } - if (global_gop.found_existing) { - // `input_resolved` allows visting this symbol in this input section's flushMoved, - // as the previously undefined global created earlier will not be in our - // contiguous first / last range. - (try coff.input_resolved.addOne(gpa)).* = si_slice[0]; - } + if (global_gop.found_existing) + num_resolved += 1; coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); }, @@ -3657,11 +3704,6 @@ fn loadObject( } } - if (coff.symbols.items.len > first_si) { - input.first_si = @enumFromInt(first_si); - input.last_si = @enumFromInt(coff.symbols.items.len - 1); - } - const relocation_size = std.coff.Relocation.sizeOf(); for (sections) |section| { if (section.si == .null) continue; @@ -3697,6 +3739,38 @@ fn loadObject( ); } } + + const symbolLessThan = struct { + fn lessThan(ctx: *Coff, lhs: Symbol.Index, rhs: Symbol.Index) bool { + const lhs_sn = @intFromEnum(if (lhs == .null) .UNDEFINED else lhs.get(ctx).section_number); + const rhs_sn = @intFromEnum(if (rhs == .null) .UNDEFINED else rhs.get(ctx).section_number); + if (lhs_sn == rhs_sn) return @intFromEnum(lhs) < @intFromEnum(rhs); + return lhs_sn < rhs_sn; + } + }.lessThan; + + std.mem.sortUnstable(Symbol.Index, symbols.items, coff, symbolLessThan); + + // Any symbols that we resolved (used to be undefined but are now defined) in this pass need to be + // added to contigous ranges in `input_resolved` so they can be visited in `flushMoved`, as they + // are not part of the contiguous ii.first_si / ii.last_si range. + // + // TODO: Should we just use this array for all symbols in this input? More memory but less get().ni misses in flushMoved + try coff.input_resolved.ensureUnusedCapacity(gpa, num_resolved); + var prev_isi: ?Node.InputSection.Index = null; + for (symbols.items) |si| { + if (si == .null or @intFromEnum(si) >= @intFromEnum(input.end_si)) continue; + const ni = si.get(coff).ni; + if (ni == .none) continue; + + const isi = coff.getNode(ni).input_section; + if (prev_isi != isi) { + isi.inputSection(coff).first_iri = @enumFromInt(coff.input_resolved.items.len); + prev_isi = isi; + } + + coff.input_resolved.addOneAssumeCapacity().* = si; + } } const ArchiveMemberHeader = struct { @@ -3813,6 +3887,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo var pos = fr.logicalPos(); const size = try fr.getSize(); while (pos < size) : (pos = fr.logicalPos()) { + if ((pos & 1) != 0) r.toss(1); const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian); const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames); @@ -3891,7 +3966,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo coff.input_archive_members.addOneAssumeCapacity().* = .{ .iai = iai, .name = undefined, - .is_import = undefined, + .flags = undefined, .file_location = .{ .offset = member_offset, .size = undefined, @@ -3952,7 +4027,10 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo const member_sig = try r.peek(4); const machine = std.mem.readInt(u16, member_sig[0..2], target_endian); const sig = std.mem.readInt(u16, member_sig[2..4], target_endian); - member.is_import = machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff; + member.flags = .{ + .is_import = machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff, + .is_loaded = false, + }; member.file_location.size = res.size; log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{ @@ -3961,7 +4039,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo member.file_location.size, }); - if (member.is_import) { + if (member.flags.is_import) { const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian); // TODO: Validate import table header fields // TODO: Use this result in flushGlobal @@ -4356,13 +4434,13 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { // TODO: We could report the name here if we interned it in loadObject err.addNote("referenced internally by input '{f}{f}'", .{ other_ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(other_ii.archiveName(coff)), + fmtMemberNameString(other_ii.memberName(coff)), }); } else { err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{ loc_sym.gmi.globalName(coff).name.toSlice(coff), other_ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(other_ii.archiveName(coff)), + fmtMemberNameString(other_ii.memberName(coff)), }); } }, @@ -4478,21 +4556,32 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; } + if (coff.pending_input) |pending_iami| { + // TODO: Prog node? + coff.flushInputMember(pending_iami) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return comp.link_diags.fail( + "linker failed to archive member: {t}", + .{e}, + ), + }; + coff.pending_input = null; + break :task; + } if (coff.global_pending_index < coff.globals.count()) { const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index); - coff.global_pending_index += 1; const sub_prog_node = coff.synth_prog_node.start( gmi.globalName(coff).name.toSlice(coff), 0, ); defer sub_prog_node.end(); - coff.flushGlobal(gmi) catch |err| switch (err) { + if (coff.flushGlobal(gmi) catch |err| switch (err) { else => |e| return e, error.MappedFileIo => return comp.link_diags.fail( "linker failed to lower constant: {t}", .{coff.mf.io_err.?}, ), - }; + }) coff.global_pending_index += 1; break :task; } var lazy_it = coff.lazy.iterator(); @@ -4547,7 +4636,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } // TODO: Idle task for flushing obj into lib? if (coff.input_section_pending_index < coff.input_sections.items.len) { - const isi: Node.InputSectionIndex = @enumFromInt(coff.input_section_pending_index); + const isi: Node.InputSection.Index = @enumFromInt(coff.input_section_pending_index); coff.input_section_pending_index += 1; const sub_prog_node = coff.idleProgNode(tid, coff.input_prog_node, coff.getNode(isi.symbol(coff).node(coff))); defer sub_prog_node.end(); @@ -4559,7 +4648,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { .{ isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(ii.archiveName(coff)), + fmtMemberNameString(ii.memberName(coff)), e, }, ); @@ -4629,9 +4718,10 @@ 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.globals.count() > coff.global_pending_index) return true; - if (coff.symbol_table.pending.count() > 0) 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; if (coff.mf.updates.items.len > 0) return true; if (coff.pending_members.count() > 0) return true; @@ -4655,7 +4745,7 @@ fn idleProgNode( const ii = isi.input(coff); break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{ ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(ii.archiveName(coff)), + fmtMemberNameString(ii.memberName(coff)), coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }) catch &name; }, @@ -4736,7 +4826,7 @@ fn flushUav( si.applyLocationRelocs(coff); } -fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { +fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const comp = coff.base.comp; const gpa = comp.gpa; const gn = gmi.globalName(coff); @@ -4751,7 +4841,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { gn.name, ); - return; + return true; } if (gn.lib_name.toSlice(coff)) |lib_name| { @@ -4911,15 +5001,26 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void { sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); } else { + if (coff.input_archive_symbol_indices.get(gn.name)) |index| { + var iter: InputArchive.Member.Symbol.Index = index.first; + while (true) { + const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; + + // TODO: This implies that loading an input failing is not fatal + if (!coff.input_archive_members.items[@intFromEnum(archive_sym.iami)].flags.is_loaded) { + coff.pending_input = archive_sym.iami; + return false; + } + + if (archive_sym.next == iter) break; + iter = archive_sym.next; + } + } - // TODO: Check if not defined, and if in an input member - // TODO: If so, queue a loadObject for that member - // TODO: Return a value indicating to retry this flushGlobal - // TODO: The loadObject idle task should be before the flushGlobal idle task - // // TODO: Check if we can get flushGlobal before prelink, that would cause a problem - } + + return true; } fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { @@ -5040,14 +5141,14 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { { var si = ii.firstSymbol(coff); - const last_si = ii.lastSymbol(coff); - while (@intFromEnum(si) <= @intFromEnum(last_si)) : (si = si.next()) { + const end_si = ii.endSymbol(coff); + while (@intFromEnum(si) < @intFromEnum(end_si)) : (si = si.next()) { if (si.get(coff).ni != ni) continue; si.flushMoved(coff); } } - for (coff.input_resolved.items[@intFromEnum(ii.firstResolvedGlobal(coff))..]) |si| { + for (coff.input_resolved.items[@intFromEnum(isi.firstResolvedSymbol(coff))..]) |si| { if (si.get(coff).ni != ni) break; si.flushMoved(coff); } @@ -5606,7 +5707,7 @@ pub fn printNode( const ii = isi.input(coff); try w.print("({f}{f}, {s})", .{ ii.path(coff).fmtEscapeString(), - fmtArchiveNameString(ii.archiveName(coff)), + fmtMemberNameString(ii.memberName(coff)), coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }); }, -- 2.54.0 From c7c1d65372019199824d5f2284a63031383f0512 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 22/94] - Remove the compiler_rt_dyn_lib hack --- lib/std/zig.zig | 4 ---- src/Compilation.zig | 39 ++----------------------------------- src/codegen/x86_64/Emit.zig | 8 -------- src/link/Coff.zig | 36 ++++++++-------------------------- 4 files changed, 10 insertions(+), 77 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index cdb2e857cd3aff3019476837147c57c50318de7e..05891c582e38646da978177e34a3e8b26ac7f0af 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -996,14 +996,11 @@ pub const EmitArtifact = enum { docs, pdb, h, - compiler_rt_dyn_lib, /// If using `Server` to communicate with the compiler, it will place requested artifacts in /// paths under the output directory, where those paths are named according to this function. /// Returned string is allocated with `gpa` and owned by the caller. pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 { - // hack for stage2_x86_64 + coff. See Coff.flush. - if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll"; const suffix: []const u8 = switch (ea) { .bin => return binNameAlloc(gpa, opts), .@"asm" => ".s", @@ -1013,7 +1010,6 @@ pub const EmitArtifact = enum { .docs => "-docs", .pdb => ".pdb", .h => ".h", - .compiler_rt_dyn_lib => unreachable, }; return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix }); } diff --git a/src/Compilation.zig b/src/Compilation.zig index 684889f2d021ea78587078d62468c85e1e4a971d..0d10f21b03939405aab6f9031100ec1cdfc68f7e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -223,8 +223,6 @@ compiler_rt_lib: ?CrtFile = null, /// Populated when we build the compiler_rt_obj object. A Job to build this is indicated /// by setting `queued_jobs.compiler_rt_obj` and resolved before calling linker.flush(). compiler_rt_obj: ?CrtFile = null, -/// hack for stage2_x86_64 + coff -compiler_rt_dyn_lib: ?CrtFile = null, /// Populated when we build the libfuzzer static library. A Job to build this /// is indicated by setting `queued_jobs.fuzzer_lib` and resolved before /// calling linker.flush(). @@ -287,8 +285,6 @@ emit_llvm_bc: ?[]const u8, emit_docs: ?[]const u8, const QueuedJobs = struct { - /// hack for stage2_x86_64 + coff - compiler_rt_dyn_lib: bool = false, compiler_rt_lib: bool = false, compiler_rt_obj: bool = false, ubsan_rt_lib: bool = false, @@ -1781,7 +1777,7 @@ fn addModuleTableToCacheHash( } } -const RtStrat = enum { none, lib, obj, zcu, dyn_lib }; +const RtStrat = enum { none, lib, obj, zcu }; pub const CreateDiagnostic = union(enum) { export_table_import_table_conflict, @@ -1902,12 +1898,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, }; if (have_zcu and (!need_llvm or use_llvm)) { if (output_mode == .Obj) break :s .zcu; - switch (target_util.zigBackend(target, use_llvm)) { - else => {}, - .stage2_aarch64, .stage2_x86_64 => if (target.ofmt == .coff) { - break :s if (is_exe_or_dyn_lib and build_options.have_llvm) .dyn_lib else .zcu; - }, - } } if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm if (is_exe_or_dyn_lib) break :s .lib; @@ -2628,11 +2618,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, log.debug("queuing a job to build compiler_rt_obj", .{}); comp.queued_jobs.compiler_rt_obj = true; }, - .dyn_lib => { - // hack for stage2_x86_64 + coff - log.debug("queuing a job to build compiler_rt_dyn_lib", .{}); - comp.queued_jobs.compiler_rt_dyn_lib = true; - }, } switch (comp.ubsan_rt_strat) { @@ -2645,7 +2630,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, log.debug("queuing a job to build ubsan_rt_obj", .{}); comp.queued_jobs.ubsan_rt_obj = true; }, - .dyn_lib => unreachable, // hack for compiler_rt only } switch (comp.zigc_strat) { @@ -2654,7 +2638,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, log.debug("queuing a job to build libzigc", .{}); comp.queued_jobs.zigc_lib = true; }, - .obj, .dyn_lib => unreachable, // only available as a static library or inside an existing ZCU + .obj => unreachable, // only available as a static library or inside an existing ZCU } if (is_exe_or_dyn_lib and comp.config.any_fuzz) { @@ -2713,7 +2697,6 @@ pub fn destroy(comp: *Compilation) void { if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io); if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io); if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io); - if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io); if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io); if (comp.glibc_so_files) |*glibc_file| { @@ -4566,24 +4549,6 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node }); } - // hack for stage2_x86_64 + coff - if (comp.queued_jobs.compiler_rt_dyn_lib and comp.compiler_rt_dyn_lib == null) { - prelink_group.async(io, buildRt, .{ - comp, - "compiler_rt.zig", - "compiler_rt", - .Lib, - .dynamic, - .compiler_rt, - main_progress_node, - RtOptions{ - .checks_valgrind = true, - .allow_lto = false, - }, - &comp.compiler_rt_dyn_lib, - }); - } - if (comp.queued_jobs.fuzzer_lib and comp.fuzzer_lib == null) { prelink_group.async(io, buildRt, .{ comp, diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index 3d0d3c1829d72bc4a297a8817aedc550e00f7396..304bde522aa2328d0f8ee7e8f4728767c14e08c7 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -154,19 +154,11 @@ pub fn emitMir(emit: *Emit) Error!void { @enumFromInt(try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)) else if (emit.bin_file.cast(.elf2)) |elf| try elf.externSymbol(.{ .name = extern_func.toSlice(&emit.lower.mir).?, - .lib_name = switch (comp.compiler_rt_strat) { - .none, .lib, .obj, .zcu => null, - .dyn_lib => "compiler_rt", - }, .type = .FUNC, }) else if (emit.bin_file.cast(.macho)) |macho_file| @enumFromInt(try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)) else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol(.{ .name = extern_func.toSlice(&emit.lower.mir).?, - .lib_name = switch (comp.compiler_rt_strat) { - .none, .lib, .obj, .zcu => null, - .dyn_lib => "compiler_rt", - }, }))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}), .is_extern = true, } }, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ec4c301057fabda514f2e43b959b28230b5551ee..de1c7e474432cd7b867bb7defdd352bdeef11e4a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -3283,7 +3283,7 @@ fn loadObject( header.machine, }); if (header.number_of_sections == 0) return; - if (@sizeOf(std.coff.Header) + header.number_of_sections * @sizeOf(std.coff.SectionHeader) > fl.size) + if (@sizeOf(std.coff.Header) + @as(usize, header.number_of_sections) * @sizeOf(std.coff.SectionHeader) > fl.size) return diags.failParse(path, "invalid section table", .{}); const unexpected_header_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{ .RELOCS_STRIPPED, @@ -4507,26 +4507,6 @@ pub fn flush( coff.flushImplib(implib_file) catch |err| return comp.link_diags.fail("flushing implib '{s}' failed: {t}", .{ implib_file, err }); - // hack for stage2_x86_64 + coff - if (comp.compiler_rt_dyn_lib) |crt_file| { - const io = comp.io; - const gpa = comp.gpa; - - const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{ - std.fs.path.dirname(coff.base.emit.sub_path) orelse "", - std.fs.path.basename(crt_file.full_object_path.sub_path), - }); - defer gpa.free(compiler_rt_sub_path); - std.Io.Dir.copyFile( - crt_file.full_object_path.root_dir.handle, - crt_file.full_object_path.sub_path, - coff.base.emit.root_dir.handle, - compiler_rt_sub_path, - io, - .{}, - ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err }); - } - coff.mf.flush() catch |err| switch (err) { error.Canceled => |e| return e, else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), @@ -4558,14 +4538,14 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } if (coff.pending_input) |pending_iami| { // TODO: Prog node? - coff.flushInputMember(pending_iami) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return comp.link_diags.fail( - "linker failed to archive member: {t}", - .{e}, - ), - }; coff.pending_input = null; + coff.flushInputMember(pending_iami) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return comp.link_diags.fail( + "linker failed to load archive member: {t}", + .{e}, + ), + }; break :task; } if (coff.global_pending_index < coff.globals.count()) { -- 2.54.0 From 6ff2b9b9137c514b2750e40bf345824461a5aabf Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 23/94] Coff: handle COMDAT sections and weak externals - Rework object loading to handle COMDAT sections - Add better error output for multiple definitions - Track all symbols associated with input sections in a flat array - Resolve weak externals to their alias if nothing defines the weak symbol - Fixup incorrect signs of reloc addends in the logic that saves / restores them --- lib/std/coff.zig | 4 + src/link/Coff.zig | 1085 +++++++++++++++++++++++++++++++-------------- 2 files changed, 753 insertions(+), 336 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index b00d965d1c5636a699bb46dd507a0e7976a2dd6c..4f5ccf7f79b78dd5a9c0c8cc3613a0ac07cebf8c 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -654,6 +654,10 @@ pub const SectionHeader = extern struct { std.debug.assert(std.math.isPowerOfTwo(n)); return @enumFromInt(@ctz(n) + 1); } + + pub fn alignment(a: Align) ?std.mem.Alignment { + return .fromByteUnitsOptional(a.toByteUnits() orelse null); + } }; }; }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index de1c7e474432cd7b867bb7defdd352bdeef11e4a..b78eea02dda57effcee48c5ca6538d815cbf4437 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -40,9 +40,10 @@ input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct { }), pending_input: ?InputArchive.Member.Index, inputs: std.ArrayList(Input), -input_resolved: std.ArrayList(Symbol.Index), +input_symbols: std.ArrayList(Symbol.Index), input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, +inputs_complete: bool, strings: std.HashMapUnmanaged( u32, void, @@ -292,25 +293,14 @@ pub const Node = union(enum) { pub fn memberName(ii: InputIndex, coff: *const Coff) ?[]const u8 { return coff.inputs.items[@intFromEnum(ii)].member_name; } - - pub fn firstSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { - return coff.inputs.items[@intFromEnum(ii)].first_si; - } - - pub fn endSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index { - return coff.inputs.items[@intFromEnum(ii)].end_si; - } - - pub fn firstResolvedGlobal(ii: InputIndex, coff: *const Coff) Input.ResolvedIndex { - return coff.inputs.items[@intFromEnum(ii)].first_iri; - } }; const InputSection = struct { ii: Node.InputIndex, si: Symbol.Index, file_location: MappedFile.Node.FileLocation, - first_iri: Node.InputSection.ResolvedIndex, + first_li: Node.InputSection.LocalIndex, + crc: u32, pub const Index = enum(u32) { _, @@ -331,12 +321,12 @@ pub const Node = union(enum) { return coff.input_sections.items[@intFromEnum(isi)].si; } - pub fn firstResolvedSymbol(isi: Index, coff: *const Coff) ResolvedIndex { - return coff.input_sections.items[@intFromEnum(isi)].first_iri; + pub fn firstSymbol(isi: Index, coff: *const Coff) LocalIndex { + return coff.input_sections.items[@intFromEnum(isi)].first_li; } }; - const ResolvedIndex = enum(u32) { + const LocalIndex = enum(u32) { _, }; }; @@ -449,8 +439,7 @@ pub const InputArchive = struct { pub const Input = struct { path: std.Build.Cache.Path, member_name: ?[]const u8, - first_si: Symbol.Index, - end_si: Symbol.Index, + source_name: String.Optional, }; pub const Member = struct { @@ -814,11 +803,14 @@ pub const GlobalName = struct { name: String, lib_name: String.Optional }; pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, - value: union { - /// For generated symbols, this is their size - size: u32, - /// For symbols from input sections, this is the offset within the input section + value: union(enum) { + /// For .ni == .input_section, this is the offset of this symbol within the input section input_offset: u32, + /// For .ni == none and .gmi != .none, this is a weak alias + /// that should replace this symbol, or .null if none exists + alias_si: Symbol.Index, + /// Otherwise, this is the symbol size if known + size: u32, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -828,7 +820,6 @@ pub const Symbol = struct { /// Only used when outputting objects sti: SymbolTable.Index, gmi: Node.GlobalMapIndex, - unused0: u16 = 0, pub const SectionNumber = enum(i16) { UNDEFINED = 0, @@ -840,6 +831,13 @@ pub const Symbol = struct { return @intCast(@intFromEnum(sn) - 1); } + fn hasIndex(sn: SectionNumber) bool { + return switch (sn) { + .UNDEFINED, .ABSOLUTE, .DEBUG => false, + else => true, + }; + } + pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index { return sn.section(coff).si; } @@ -1021,15 +1019,21 @@ pub const Reloc = extern struct { ), .ADDR32, .ADDR32NB, + .SECREL, + => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(reloc.addend), + target_endian, + ), .REL32, .REL32_1, .REL32_2, .REL32_3, .REL32_4, .REL32_5, - .SECREL, => std.mem.writeInt( - u32, + i32, loc_slice[0..4], @intCast(reloc.addend), target_endian, @@ -1039,16 +1043,21 @@ pub const Reloc = extern struct { else => |kind| @panic(@tagName(kind)), .ABSOLUTE => {}, .DIR16, - .REL16, => std.mem.writeInt( u16, loc_slice[0..2], @intCast(reloc.addend), target_endian, ), + .REL16, + => std.mem.writeInt( + i16, + loc_slice[0..2], + @intCast(reloc.addend), + target_endian, + ), .DIR32, .DIR32NB, - .REL32, .SECREL, => std.mem.writeInt( u32, @@ -1056,6 +1065,13 @@ pub const Reloc = extern struct { @intCast(reloc.addend), target_endian, ), + .REL32, + => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(reloc.addend), + target_endian, + ), }, } @@ -1074,15 +1090,20 @@ pub const Reloc = extern struct { )), .ADDR32, .ADDR32NB, + .SECREL, + => std.mem.readInt( + u32, + loc_slice[0..4], + target_endian, + ), .REL32, .REL32_1, .REL32_2, .REL32_3, .REL32_4, .REL32_5, - .SECREL, => std.mem.readInt( - u32, + i32, loc_slice[0..4], target_endian, ), @@ -1091,21 +1112,31 @@ pub const Reloc = extern struct { else => |kind| @panic(@tagName(kind)), .ABSOLUTE => 0, .DIR16, - .REL16, => std.mem.readInt( u16, loc_slice[0..2], target_endian, ), + .REL16, + => std.mem.readInt( + i16, + loc_slice[0..2], + target_endian, + ), .DIR32, .DIR32NB, - .REL32, .SECREL, => std.mem.readInt( u32, loc_slice[0..4], target_endian, ), + .REL32, + => std.mem.readInt( + i32, + loc_slice[0..4], + target_endian, + ), }, }; } @@ -1356,9 +1387,10 @@ fn create( .input_archive_symbol_indices = .empty, .pending_input = null, .inputs = .empty, - .input_resolved = .empty, + .input_symbols = .empty, .input_sections = .empty, .input_section_pending_index = 0, + .inputs_complete = false, .strings = .empty, .string_bytes = .empty, .section_table = .empty, @@ -1420,7 +1452,7 @@ pub fn deinit(coff: *Coff) void { coff.input_archive_symbols.deinit(gpa); coff.input_archive_symbol_indices.deinit(gpa); coff.inputs.deinit(gpa); - coff.input_resolved.deinit(gpa); + coff.input_symbols.deinit(gpa); coff.input_sections.deinit(gpa); coff.strings.deinit(gpa); coff.string_bytes.deinit(gpa); @@ -2238,13 +2270,6 @@ fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index { return si; } -fn initInputSectionSymbol(coff: *Coff, sym: *Symbol, section_si: Symbol.Index, value: u32) void { - const section_sym = section_si.get(coff); - sym.ni = section_sym.ni; - sym.value = .{ .input_offset = value }; - sym.section_number = section_sym.section_number; -} - fn getOrPutString(coff: *Coff, string: []const u8) !String { try coff.ensureUnusedStringCapacity(string.len); return coff.getOrPutStringAssumeCapacity(string); @@ -2315,7 +2340,7 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { } const GlobalOptions = struct { - name: []const u8, + name: []const u8, // TODO: Union with String lib_name: ?[]const u8 = null, }; @@ -2986,7 +3011,8 @@ fn objectSectionMapIndex( attributes: ObjectSectionAttributes, ) !Node.ObjectSectionMapIndex { const gpa = coff.base.comp.gpa; - const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name.toSlice(coff), ".tls")) attr: { + const name_slice = name.toSlice(coff); + const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name_slice, ".tls")) attr: { // In images, the .tls section is a read-only template var attr = attributes; attr.write = false; @@ -2996,8 +3022,7 @@ fn objectSectionMapIndex( const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index); const sn = if (!object_section_gop.found_existing) sn: { - try coff.ensureUnusedStringCapacity(name.toSlice(coff).len); - const name_slice = name.toSlice(coff); + try coff.ensureUnusedStringCapacity(name_slice.len); const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice)); const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); @@ -3047,6 +3072,7 @@ fn objectSectionMapIndex( return osmi; } +// TODO: Include align in attrs and verify the current align is >= requested fn verifyParentSectionAttributes( coff: *Coff, kind: enum { pseudo, object }, @@ -3096,7 +3122,8 @@ pub fn addReloc( const gpa = coff.base.comp.gpa; const target = target_si.get(coff); - log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d}{s})", .{ + const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len); + log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d}{s}) = {d}", .{ loc_si, loc_si.get(coff).section_number, offset, @@ -3104,6 +3131,7 @@ pub fn addReloc( target_si.get(coff).section_number, if (addend == .pending) 0 else addend.known, if (addend == .pending) "p" else "k", + ri, }); try coff.relocs.ensureUnusedCapacity(gpa, 1); @@ -3166,7 +3194,6 @@ pub fn addReloc( }, }; - const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len); coff.relocs.addOneAssumeCapacity().* = .{ .type = @"type", .prev = .none, @@ -3319,8 +3346,7 @@ fn loadObject( input.* = .{ .path = path, .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null, - .first_si = @enumFromInt(coff.symbols.items.len), - .end_si = @enumFromInt(coff.symbols.items.len), + .source_name = .none, }; const string_table = string_table: { @@ -3336,30 +3362,60 @@ fn loadObject( string_table_len - @sizeOf(u32), ); - const InputSection = struct { + const PendingSymbolIndex = enum(u32) { + none, + _, + + pub fn wrap(i: ?u32) @This() { + return @enumFromInt((i orelse return .none) + 1); + } + + pub fn unwrap(i: @This()) ?u32 { + return switch (i) { + .none => null, + _ => @intFromEnum(i) - 1, + }; + } + }; + + const PendingInputSection = struct { header: std.coff.SectionHeader, name: String, si: Symbol.Index, + parent_si: Symbol.Index, + psi: PendingSymbolIndex, + num_symbols: u32, + comdat: std.coff.ComdatSelection, + comdat_psi: PendingSymbolIndex, + comdat_crc: u32, + comdat_association: Symbol.SectionNumber, + comdat_result: union(enum) { + pending, + // Root of the association chain + pending_association: Symbol.SectionNumber, + include, + skip, + }, }; - const sections: []const InputSection = if (coff.isImage()) sections: { - const sections = try gpa.alloc(InputSection, header.number_of_sections); + const sections: []PendingInputSection = if (coff.isImage()) sections: { + const sections = try gpa.alloc(PendingInputSection, header.number_of_sections); errdefer gpa.free(sections); - var num_input_sections: u16 = 0; - var reqd_object_sections: std.AutoArrayHashMapUnmanaged(String, void) = .empty; - defer reqd_object_sections.deinit(gpa); - var reqd_pseudo_sections: std.StringArrayHashMapUnmanaged(void) = .empty; - defer reqd_pseudo_sections.deinit(gpa); - try reqd_object_sections.ensureUnusedCapacity(gpa, sections.len); - try reqd_pseudo_sections.ensureUnusedCapacity(gpa, sections.len); - try fr.seekTo(fl.offset + @sizeOf(std.coff.Header)); for (sections, 0..) |*section, section_i| { section.* = .{ .header = try r.takeStruct(std.coff.SectionHeader, target_endian), .name = undefined, .si = .null, + .parent_si = .null, + .psi = .none, + .num_symbols = 0, + .comdat = .NONE, + .comdat_psi = .none, + .comdat_crc = 0, + .comdat_association = .UNDEFINED, + .comdat_result = .pending, }; const section_name_slice = if (section.header.name[0] == '/') name: { @@ -3371,7 +3427,11 @@ fn loadObject( }); if (name_offset > string_table.len) - return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset }); + return diags.failParse( + path, + "out-of-bounds section name offset in section {d}: {d}", + .{ section_i, name_offset }, + ); break :name std.mem.sliceTo(string_table[name_offset..], 0); } else std.mem.sliceTo(§ion.header.name, 0); @@ -3403,109 +3463,6 @@ fn loadObject( // TODO: Convert .debug$* sections into PDB continue; } - - num_input_sections += 1; - _ = reqd_object_sections.getOrPutAssumeCapacity(section.name); - _ = reqd_pseudo_sections.getOrPutAssumeCapacity( - coff.objectSectionParentName(section.name.toSlice(coff)), - ); - } - - var symbol_capacity: u16 = num_input_sections; - var node_capacity: u16 = 0; - { - var iter = reqd_object_sections.count(); - while (iter > 0) { - iter -= 1; - if (coff.object_section_table.contains(reqd_object_sections.keys()[iter])) - reqd_object_sections.swapRemoveAt(iter); - } - - node_capacity += @intCast(reqd_object_sections.count()); - symbol_capacity += @intCast(reqd_object_sections.count()); - } - - { - var iter = reqd_pseudo_sections.count(); - while (iter > 0) { - // TODO: Track the extra number of strings and their length and reserve? These have not been reserved as - // part of the ensureManyUnusedStringCapacity call above - iter -= 1; - const name = coff.getString(reqd_pseudo_sections.keys()[iter]) orelse continue; - if (coff.pseudo_section_table.contains(name)) - reqd_pseudo_sections.swapRemoveAt(iter); - } - - node_capacity += @intCast(reqd_pseudo_sections.count()); - symbol_capacity += @intCast(reqd_pseudo_sections.count()); - } - - try coff.nodes.ensureUnusedCapacity(gpa, node_capacity); - try coff.symbols.ensureUnusedCapacity(gpa, symbol_capacity + num_input_sections); - try coff.input_sections.ensureUnusedCapacity(gpa, num_input_sections); - - for (sections) |*section| { - if (section.header.flags.LNK_INFO) { - if (std.mem.eql(u8, §ion.header.name, ".drectve")) { - try fr.seekTo(fl.offset + section.header.pointer_to_raw_data); - var buf: [128]u8 = undefined; - var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf); - while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) { - error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}), - else => |e| return e, - }) |arg| { - // Microsoft tools emit 3 space characters into this section even with /Zl - if (arg.len > 0) - return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg}); - } - } - - continue; - } - - if (section.header.flags.LNK_REMOVE or - section.header.flags.MEM_DISCARDABLE) - { - continue; - } - - if (section.header.flags.LNK_COMDAT) - // This will be necessary if we do the equivalent of /Gy for compiler-rt - return diags.failParse(path, "TODO handle COMDAT sections in input objects", .{}); - - const parent_osmi = try coff.objectSectionMapIndex( - section.name, - coff.mf.flags.block_size, - .fromFlags(section.header.flags), - ); - const parent_si = parent_osmi.symbol(coff); - const ni = try coff.mf.addLastChildNode(gpa, parent_si.node(coff), .{ - .size = section.header.size_of_raw_data, - .alignment = if (section.header.flags.ALIGN.toByteUnits()) |align_bytes| - .fromByteUnits(align_bytes) - else - .@"1", - .moved = true, - }); - coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) }); - - section.si = coff.addSymbolAssumeCapacity(); - const sym = section.si.get(coff); - sym.ni = ni; - sym.section_number = parent_si.get(coff).section_number; - - coff.input_sections.addOneAssumeCapacity().* = .{ - .ii = ii, - .si = section.si, - .file_location = .{ - .offset = fl.offset + section.header.pointer_to_raw_data, - .size = section.header.size_of_raw_data, - }, - .first_iri = @enumFromInt(coff.input_resolved.items.len), - }; - - log.debug("loadInputSection({s}) = {d}@{d}", .{ section.name.toSlice(coff), section.si, sym.section_number }); - coff.synth_prog_node.increaseEstimatedTotalItems(1); } break :sections sections; @@ -3522,9 +3479,8 @@ fn loadObject( const member = mi.get(coff); try member.initHeader(coff, path_str, header.time_date_stamp); - // TODO: This could be deferred to an idle task? - { + // TODO: This could be deferred to an idle task? var nw: MappedFile.Node.Writer = undefined; member.content_ni.writer(&coff.mf, gpa, &nw); defer nw.deinit(); @@ -3537,18 +3493,35 @@ fn loadObject( break :mi mi; } else undefined; - // TODO: Also reserve memory for the symbols / globals / relocs within each section - try fr.seekTo(fl.offset + header.pointer_to_symbol_table); - const symbol_size = comptime std.coff.Symbol.sizeOf(); + const symbol_size = std.coff.Symbol.sizeOf(); - var symbols: std.ArrayList(Symbol.Index) = .empty; - try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); - var num_resolved: u32 = 0; + const PendingSymbol = struct { + name: String, + value: union(enum) { + // Size of the section + section: u32, + // Offset within the section + static: u32, + // If section is defined, the symbol size. Otherwise offset within the section. + external: u32, + // The index of the target symbol of this alias + weak_external: u32, + }, + section_number: Symbol.SectionNumber, + si: Symbol.Index, + // The index of the weak_external that targest this symbol + weak_external_psi: PendingSymbolIndex, + }; - input.first_si = @enumFromInt(coff.symbols.items.len); - defer input.end_si = @enumFromInt(coff.symbols.items.len); + var num_global_symbols: u32 = 0; + var pending_symbols: std.AutoArrayHashMapUnmanaged(u32, PendingSymbol) = .empty; + defer pending_symbols.deinit(gpa); + if (!is_archive) + try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); + + // Discover symbol names and COMDAT symbol mappings var symbol_i: u32 = 0; while (symbol_i < header.number_of_symbols) { var symbol: std.coff.Symbol = undefined; @@ -3556,10 +3529,11 @@ fn loadObject( if (target_endian != native_endian) std.mem.byteSwapAllFields(std.coff.Symbol, &symbol); - defer { - r.toss(symbol.number_of_aux_symbols * symbol_size); - symbol_i += symbol.number_of_aux_symbols + 1; - } + const aux_symbols = if (symbol.number_of_aux_symbols > 0) + try r.take(symbol_size * symbol.number_of_aux_symbols) + else + &.{}; + defer symbol_i += symbol.number_of_aux_symbols + 1; const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], target_endian); @@ -3568,145 +3542,499 @@ fn loadObject( break :name string_table[index..]; } else &symbol.name, 0); - const si_slice = symbols.addManyAsSliceAssumeCapacity(1 + symbol.number_of_aux_symbols); - @memset(si_slice, .null); - - defer log.debug("loadInputSymbol({s}, 0x{x}) = {d}@{d}", .{ - name, - symbol.value, - si_slice[0], - if (si_slice[0] == .null) .UNDEFINED else si_slice[0].get(coff).section_number, - }); - if (is_archive) { - if (symbol.storage_class == .EXTERNAL and symbol.section_number != .UNDEFINED) - try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); + if (switch (symbol.storage_class) { + .WEAK_EXTERNAL => true, + .EXTERNAL => symbol.section_number != .UNDEFINED, + else => false, + }) try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name)); continue; } - switch (symbol.storage_class) { - .STATIC, .LABEL => |storage_class| switch (symbol.section_number) { - .UNDEFINED, .DEBUG, .ABSOLUTE => { - // TODO: Do we need to do anything with @feat.00? - // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392 - }, + switch (symbol.section_number) { + .UNDEFINED, .DEBUG, .ABSOLUTE => {}, + else => |sn| if (@intFromEnum(sn) > sections.len) + return diags.failParse(path, "out-of-bounds section number {d} in symbol 0x{x}", .{ sn, symbol_i }), + } + + const psi: PendingSymbolIndex = .wrap(@intCast(pending_symbols.count())); + const section_number: Symbol.SectionNumber = @enumFromInt(@intFromEnum(symbol.section_number)); + const opt_value: ?@FieldType(PendingSymbol, "value") = pending_symbol: switch (symbol.storage_class) { + .STATIC, .LABEL => |storage_class| switch (section_number) { + // TODO: Do we need to do anything with @feat.00? + // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392 + .UNDEFINED, .DEBUG, .ABSOLUTE => null, else => |sn| { + const section = §ions[sn.toIndex()]; + // Section symbol - if (storage_class == .STATIC and + const is_section = storage_class == .STATIC and symbol.value == 0 and symbol.type == std.coff.SymType{ .complex_type = .NULL, .base_type = .NULL, } and - symbol.number_of_aux_symbols > 0) - { + symbol.number_of_aux_symbols > 0; + + if (is_section) { if (symbol.number_of_aux_symbols > 1) - return diags.failParse(path, "invalid number of aux symbols for section 0x{x}: {d}", .{ + return diags.failParse(path, "invalid number of aux symbols for section symbol 0x{x}: {d}", .{ symbol_i, symbol.number_of_aux_symbols, }); var section_def: std.coff.SectionDefinition = undefined; - @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], try r.peek(symbol_size)); + @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], aux_symbols[0..symbol_size]); if (target_endian != native_endian) std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def); - // TODO: Extract the COMDAT section info - - if (section_def.number > sections.len) - return diags.failParse( - path, - "section symbol for '{s}' contained an out of bounds section number: 0x{x}", - .{ name, section_def.number }, - ); - - // It's valid for this to not match the symbol's section number (ie. .drectve sets this) - if (section_def.number == 0) - continue; - - const section = §ions[section_def.number - 1]; if (section_def.number_of_relocations != section.header.number_of_relocations) return diags.failParse( path, - "section symbol for '{s}' relocation count did not match section header: {d} vs {d}", - .{ name, section_def.number_of_relocations, section.header.number_of_relocations }, + "section aux symbol 0x{x} for '{s}' relocation count did not match section header: {d} vs {d}", + .{ symbol_i + 1, name, section_def.number_of_relocations, section.header.number_of_relocations }, ); if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) return diags.failParse( path, - "section symbol for '{s}' line number count did not match section header: {d} vs {d}", - .{ name, section_def.number_of_linenumbers, section.header.number_of_linenumbers }, + "section aux symbol 0x{x} for '{s}' line number count did not match section header: {d} vs {d}", + .{ symbol_i + 1, name, section_def.number_of_linenumbers, section.header.number_of_linenumbers }, ); - @memset(si_slice, section.si); - } else { - try coff.symbols.ensureUnusedCapacity(gpa, 1); - si_slice[0] = coff.addSymbolAssumeCapacity(); - const sym = si_slice[0].get(coff); - coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); + if (section.header.flags.LNK_COMDAT) { + if (section_def.selection == .ASSOCIATIVE) { + if (section_def.number == 0 or section_def.number > sections.len) + return diags.failParse( + path, + "section aux symbol 0x{x} for '{s}' contained an invalid associated section number: 0x{x}", + .{ symbol_i + 1, name, section_def.number }, + ); + + section.comdat_association = @enumFromInt(section_def.number); + } + + section.comdat = section_def.selection; + section.comdat_crc = section_def.checksum; + } + + section.psi = psi; } + + break :pending_symbol if (is_section) + .{ .section = section.header.size_of_raw_data } + else + .{ .static = symbol.value }; }, }, - .EXTERNAL => switch (symbol.section_number) { + .WEAK_EXTERNAL => switch (symbol.section_number) { .UNDEFINED => { - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); - si_slice[0] = global_gop.value_ptr.*; - if (!global_gop.found_existing) { - const sym = si_slice[0].get(coff); - sym.value = .{ .size = symbol.value }; - } + if (symbol.value != 0) + return diags.failParse( + path, + "invalid value {d} for weak external symbol 0x{x}", + .{ symbol.value, symbol_i }, + ); + + var weak_external: std.coff.WeakExternalDefinition = undefined; + @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]); + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external); + + if (weak_external.tag_index >= header.number_of_symbols) + return diags.failParse( + path, + "invalid tag_index 0x{x} for weak external symbol 0x{x}", + .{ weak_external.tag_index, symbol_i }, + ); + + break :pending_symbol switch (weak_external.flag) { + .SEARCH_NOLIBRARY, + .SEARCH_LIBRARY, + => return diags.failParse( + path, + "TODO handle weak external characteristic 0x{x} for symbol 0x{x}", + .{ weak_external.flag, symbol_i }, + ), + .SEARCH_ALIAS => .{ .weak_external = weak_external.tag_index }, + else => return diags.failParse( + path, + "encountered unknown weak external characteristic 0x{x} for symbol 0x{x}", + .{ weak_external.flag, symbol_i }, + ), + }; }, + else => |sn| return diags.failParse( + path, + "invalid section number {d} for weak external symbol 0x{x}", + .{ sn, symbol_i }, + ), + }, + .EXTERNAL => switch (section_number) { + .UNDEFINED => .{ .external = symbol.value }, .ABSOLUTE => return diags.failParse( path, - "TODO unhandled external absolute symbol: '{s}'", - .{name}, + "TODO unhandled external absolute symbol 0x{x}: '{s}'", + .{ symbol_i, name }, ), .DEBUG => return diags.failParse( path, - "unexpected external symbol in DEBUG section: '{s}'", - .{name}, + "unexpected external symbol 0x{x} in DEBUG section: '{s}'", + .{ symbol_i, name }, ), - else => |sn| { - // TODO: Should this use archive name as lib_name as well? - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name }); - si_slice[0] = global_gop.value_ptr.*; - const sym = si_slice[0].get(coff); - if (global_gop.found_existing and sym.ni != .none) { - // TODO: Need corresponding logic later if we try to make a global already defined by an input - var err = try diags.addErrorWithNotes(2); - try err.addMsg("multiple definitions of '{s}'", .{name}); - switch (coff.getNode(sym.ni)) { - .input_section => |isi| { - const other_ii = isi.input(coff); - err.addNote("first seen in input '{f}{f}'", .{ - other_ii.path(coff).fmtEscapeString(), - fmtMemberNameString(other_ii.memberName(coff)), - }); - }, - .nav, .uav => err.addNote("first seen in module '{s}'", .{ - comp.zcu.?.root_mod.fully_qualified_name, - }), - else => unreachable, + else => .{ .external = symbol.value }, + }, + .FILE => { + if (!std.mem.eql(u8, name, ".file")) + return diags.failParse( + path, + "unexpected symbol name '{s}' for file symbol 0x{x}", + .{ name, symbol_i }, + ); + + var file: std.coff.FileDefinition = undefined; + @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]); + + input.source_name = (try coff.getOrPutString(file.getFileName())).toOptional(); + break :pending_symbol null; + }, + else => |storage_class| return diags.failParse( + path, + "TODO handle storage class {t} for symbol 0x{x}", + .{ storage_class, symbol_i }, + ), + }; + + if (opt_value) |value| { + switch (value) { + .section => {}, + .static, .external, .weak_external => { + num_global_symbols += 1; + if (section_number.hasIndex()) { + const section = §ions[section_number.toIndex()]; + section.num_symbols += 1; + if (section.header.flags.LNK_COMDAT and section.comdat_psi == .none) + section.comdat_psi = psi; + } + }, + } + + const symbol_name = coff.getOrPutStringAssumeCapacity(name); + pending_symbols.putAssumeCapacity(symbol_i, .{ + .name = symbol_name, + .value = value, + .section_number = section_number, + .si = .null, + .weak_external_psi = .none, + }); + } + } + + try coff.globals.ensureUnusedCapacity(gpa, num_global_symbols); + for (sections) |*section| { + if (section.header.flags.LNK_INFO) { + if (std.mem.eql(u8, §ion.header.name, ".drectve")) { + try fr.seekTo(fl.offset + section.header.pointer_to_raw_data); + var buf: [128]u8 = undefined; + var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf); + while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) { + error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}), + else => |e| return e, + }) |arg| { + // Microsoft tools emit 3 space characters into this section even with /Zl + if (arg.len > 0) + return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg}); + } + } + + section.comdat_result = .skip; + continue; + } + + if (section.header.flags.LNK_REMOVE or + section.header.flags.MEM_DISCARDABLE) + { + section.comdat_result = .skip; + continue; + } + + section.comdat_result = comdat: switch (section.comdat) { + .NONE => .include, + .ASSOCIATIVE => { + // Associative COMDAT sections have no COMDAT symbol. + // They are linked if the assocated section is linked. + var iter = section; + var iter_sn = iter.comdat_association; + while (iter.comdat == .ASSOCIATIVE) { + iter = §ions[iter_sn.toIndex()]; + iter_sn = iter.comdat_association; + if (iter == section) + return diags.failParse( + path, + "circular COMDAT association loop detected, starting at symbol 0x{x}", + .{pending_symbols.keys()[section.psi.unwrap().?]}, + ); + } + + assert(iter != section); + break :comdat switch (iter.comdat_result) { + .pending => .{ .pending_association = iter_sn }, + else => |iter_result| iter_result, + }; + }, + else => |comdat| { + const psi = section.comdat_psi.unwrap() orelse + return diags.failParse( + path, + "COMDAT section symbol 0x{x} had no COMDAT symbol", + .{pending_symbols.keys()[section.psi.unwrap().?]}, + ); + + const symbol = &pending_symbols.values()[psi]; + switch (symbol.value) { + .section, .weak_external => unreachable, + .static => break :comdat .include, + else => {}, + } + + // TODO: Do we need to use lib_name here? + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff), .lib_name = null }); + if (!global_gop.found_existing) { + symbol.si = global_gop.value_ptr.*; + break :comdat .include; + } + + const index = pending_symbols.keys()[psi]; + const si = global_gop.value_ptr.*; + switch (comdat) { + .NODUPLICATES => return coff.failMultipleDefinitions( + path, + member_name, + symbol.name, + index, + si, + .duplicate, + ), + .ANY => break :comdat .skip, + .SAME_SIZE => { + // TODO: Verify that this node isn't resized after creation + _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf); + if (size == section.header.size_of_raw_data) + break :comdat .skip; + + return coff.failMultipleDefinitions( + path, + member_name, + symbol.name, + index, + si, + .{ .size = .{ .a = size, .b = section.header.size_of_raw_data } }, + ); + }, + .EXACT_MATCH => { + const sym = si.get(coff); + const existing_crc = switch (coff.getNode(sym.ni)) { + .input_section => |isi| isi.inputSection(coff).crc, + // TODO: Should this result be cached somewhere? + else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)), + }; + + if (existing_crc == section.comdat_crc) + break :comdat .skip; + + return coff.failMultipleDefinitions( + path, + member_name, + symbol.name, + index, + si, + .{ .crc = .{ .a = existing_crc, .b = section.comdat_crc } }, + ); + }, + .LARGEST => { + // TODO: Resize existing .ni and replace with this section's contents + // TODO: This will be tricky, what to do about existing InputSection? + unreachable; // TODO + }, + .NONE, .ASSOCIATIVE, _ => unreachable, + } + }, + }; + } + + // Resolve pending associations, create parent sections + var num_included_sections: u16 = 0; + var num_included_symbols: u32 = 0; + var num_included_relocs: u32 = 0; + for (sections) |*section| { + comdat: switch (section.comdat_result) { + .pending_association => |root_assoc_sn| { + const root_result = sections[root_assoc_sn.toIndex()].comdat_result; + assert(root_result != .pending_association); + section.comdat_result = root_result; + continue :comdat root_result; + }, + .include => {}, + .skip => continue, + .pending => unreachable, + } + + num_included_sections += 1; + num_included_symbols += section.num_symbols; + num_included_relocs += section.header.number_of_relocations; + + section.parent_si = (try coff.objectSectionMapIndex( + section.name, + section.header.flags.ALIGN.alignment() orelse .@"1", + .fromFlags(section.header.flags), + )).symbol(coff); + } + + try coff.nodes.ensureUnusedCapacity(gpa, num_included_sections); + try coff.relocs.ensureUnusedCapacity(gpa, num_included_relocs); + try coff.symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections); + try coff.input_sections.ensureUnusedCapacity(gpa, num_included_sections); + + for (sections) |*section| { + if (section.comdat_result != .include) continue; + + const ni = try coff.mf.addLastChildNode(gpa, section.parent_si.node(coff), .{ + .size = section.header.size_of_raw_data, + .alignment = section.header.flags.ALIGN.alignment() orelse .@"1", + .moved = true, + }); + coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) }); + + section.si = coff.addSymbolAssumeCapacity(); + if (section.psi.unwrap()) |psi| + pending_symbols.values()[psi].si = section.si; + + const sym = section.si.get(coff); + sym.ni = ni; + sym.section_number = section.parent_si.get(coff).section_number; + + coff.input_sections.addOneAssumeCapacity().* = .{ + .ii = ii, + .si = section.si, + .file_location = .{ + .offset = fl.offset + section.header.pointer_to_raw_data, + .size = section.header.size_of_raw_data, + }, + .first_li = @enumFromInt(coff.input_symbols.items.len), + .crc = section.comdat_crc, + }; + + log.debug( + "addInputSection({s}, 0x{x}) = {d}@{d}", + .{ section.name.toSlice(coff), section.comdat_crc, section.si, sym.section_number }, + ); + coff.synth_prog_node.increaseEstimatedTotalItems(1); + } + + for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, psi| { + const section = switch (symbol.section_number) { + .UNDEFINED => switch (symbol.value) { + .section, + .static, + => unreachable, + .external, + .weak_external, + => |value, tag| { + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + symbol.si = global_gop.value_ptr.*; + if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { + const sym = symbol.si.get(coff); + if (tag == .external) { + // TOOD: Is it valid to encounter multiple external definitions with different sizes? + assert(sym.value == .size); + sym.value = .{ .size = @max(sym.value.size, value) }; + } else { + const alias = pending_symbols.getPtr(value) orelse + return diags.failParse( + path, + "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}", + .{ + index, + symbol.name.toSlice(coff), + fmtMemberNameString(member_name), + value, + }, + ); + + if (alias.si == .null) { + alias.weak_external_psi = .wrap(@intCast(psi)); + } else { + sym.value = .{ .alias_si = alias.si }; + } } - err.addNote("defined again in input '{f}'", .{path}); - return error.LinkFailure; } - if (global_gop.found_existing) - num_resolved += 1; - - coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value); + continue; }, }, - else => {}, + .ABSOLUTE, .DEBUG => continue, + else => |sn| §ions[sn.toIndex()], + }; + + if (section.si == .null) + continue; + + if (symbol.si == .null) { + switch (symbol.value) { + .section => unreachable, + .static => { + symbol.si = coff.addSymbolAssumeCapacity(); + }, + .external => { + // COMDAT symbols were created when enumerating the sections + assert(section.comdat == .NONE); + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + symbol.si = global_gop.value_ptr.*; + + const sym = symbol.si.get(coff); + if (global_gop.found_existing and sym.ni != .none) + return coff.failMultipleDefinitions(path, member_name, symbol.name, index, global_gop.value_ptr.*, .none); + }, + .weak_external => unreachable, + } } + + if (symbol.weak_external_psi.unwrap()) |i| { + assert(symbol.si != .null); + pending_symbols.values()[i].si.get(coff).value = .{ .alias_si = symbol.si }; + } + + if (section.si != symbol.si) { + const sym = symbol.si.get(coff); + sym.ni = section.si.get(coff).ni; + sym.value = switch (symbol.value) { + .section => |v| .{ .size = v }, + .static => |v| .{ .input_offset = v }, + .external => |v| switch (symbol.section_number) { + .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable, + else => .{ .input_offset = v }, + }, + .weak_external => unreachable, + }; + sym.section_number = symbol.section_number; + } + + defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}) = {d}@{d}", .{ + symbol.name.toSlice(coff), + index, + symbol.value, + switch (symbol.value) { + inline else => |v| v, + }, + symbol.si, + section.si.get(coff).section_number, + }); } const relocation_size = std.coff.Relocation.sizeOf(); for (sections) |section| { - if (section.si == .null) continue; + if (section.comdat_result != .include) continue; const loc_sym = section.si.get(coff); assert(loc_sym.loc_relocs == .none); @@ -3714,7 +4042,6 @@ fn loadObject( if (section.header.number_of_relocations == 0) continue; - try coff.relocs.ensureUnusedCapacity(gpa, section.header.number_of_relocations); try fr.seekTo(fl.offset + section.header.pointer_to_relocations); for (0..section.header.number_of_relocations) |reloc_i| { var reloc: std.coff.Relocation = undefined; @@ -3722,57 +4049,110 @@ fn loadObject( if (target_endian != native_endian) std.mem.byteSwapAllFields(std.coff.Relocation, &reloc); - if (reloc.symbol_table_index >= symbols.items.len) + // TODO: This error should show member name for lib + const symbol = pending_symbols.get(reloc.symbol_table_index) orelse return diags.failParse( path, - "relocation 0x{x} in section '{s}' targets invalid symbol index 0x{x}", - .{ reloc_i, section.name.toSlice(coff), reloc.symbol_table_index }, + "relocation 0x{x} in section '{s}'{f} targets invalid symbol index 0x{x}", + .{ reloc_i, section.name.toSlice(coff), fmtMemberNameString(member_name), reloc.symbol_table_index }, ); - assert(symbols.items[reloc.symbol_table_index] != .null); + assert(symbol.si != .null); try coff.addReloc( section.si, reloc.virtual_address - section.header.virtual_address, - symbols.items[reloc.symbol_table_index], + symbol.si, .pending, @bitCast(reloc.type), ); } } - const symbolLessThan = struct { - fn lessThan(ctx: *Coff, lhs: Symbol.Index, rhs: Symbol.Index) bool { - const lhs_sn = @intFromEnum(if (lhs == .null) .UNDEFINED else lhs.get(ctx).section_number); - const rhs_sn = @intFromEnum(if (rhs == .null) .UNDEFINED else rhs.get(ctx).section_number); - if (lhs_sn == rhs_sn) return @intFromEnum(lhs) < @intFromEnum(rhs); - return lhs_sn < rhs_sn; + // Set up contiguous symbol ranges in `input_symbols` for both symbols we just created, + // and symbols that were previously created as undefined, but we just defined. + const SortContext = struct { + v: []const PendingSymbol, + + pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { + const lhs = &ctx.v[a_index]; + const rhs = &ctx.v[b_index]; + if (lhs.section_number == rhs.section_number) + return @intFromEnum(lhs.si) < @intFromEnum(rhs.si); + return @intFromEnum(lhs.section_number) < @intFromEnum(rhs.section_number); } - }.lessThan; - - std.mem.sortUnstable(Symbol.Index, symbols.items, coff, symbolLessThan); - - // Any symbols that we resolved (used to be undefined but are now defined) in this pass need to be - // added to contigous ranges in `input_resolved` so they can be visited in `flushMoved`, as they - // are not part of the contiguous ii.first_si / ii.last_si range. - // - // TODO: Should we just use this array for all symbols in this input? More memory but less get().ni misses in flushMoved - try coff.input_resolved.ensureUnusedCapacity(gpa, num_resolved); - var prev_isi: ?Node.InputSection.Index = null; - for (symbols.items) |si| { - if (si == .null or @intFromEnum(si) >= @intFromEnum(input.end_si)) continue; - const ni = si.get(coff).ni; - if (ni == .none) continue; - - const isi = coff.getNode(ni).input_section; - if (prev_isi != isi) { - isi.inputSection(coff).first_iri = @enumFromInt(coff.input_resolved.items.len); - prev_isi = isi; + }; + + pending_symbols.sortUnstable(SortContext{ .v = pending_symbols.values() }); + + try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections); + var prev_sn: Symbol.SectionNumber = .UNDEFINED; + for (pending_symbols.values()) |symbol| { + // The symbol may have not been included, or it's an undefined external + if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue; + assert(coff.getNode(symbol.si.get(coff).ni) == .input_section); + + if (prev_sn != symbol.section_number) { + prev_sn = symbol.section_number; + + const section = §ions[symbol.section_number.toIndex()]; + const isi = coff.getNode(section.si.get(coff).ni).input_section; + isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len); } - coff.input_resolved.addOneAssumeCapacity().* = si; + coff.input_symbols.addOneAssumeCapacity().* = symbol.si; } } +fn failMultipleDefinitions( + coff: *Coff, + path: std.Build.Cache.Path, + member_name: ?[]const u8, + name: String, + index: u32, + existing_si: Symbol.Index, + comdat_reason: union(enum) { + none: void, + duplicate: void, + size: struct { a: u64, b: u64 }, + crc: struct { a: u32, b: u32 }, + }, +) error{ LinkFailure, OutOfMemory } { + const num_notes: usize = 2 + @as(usize, @intFromBool(comdat_reason != .none)); + var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes); + try err.addMsg("multiple definitions of '{s}'", .{name.toSlice(coff)}); + + switch (coff.getNode(existing_si.get(coff).ni)) { + .input_section => |isi| { + const other_ii = isi.input(coff); + err.addNote("first seen in input '{f}{f}'", .{ + other_ii.path(coff).fmtEscapeString(), + fmtMemberNameString(other_ii.memberName(coff)), + }); + }, + .nav, .uav => err.addNote("first seen in module '{s}'", .{ + coff.base.comp.zcu.?.root_mod.fully_qualified_name, + }), + //else => |_, tag| err.addNote("TODO multiple def for {t}", .{tag}), + else => unreachable, + } + + err.addNote("defined again in input '{f}{f}' (0x{x}))", .{ path, fmtMemberNameString(member_name), index }); + switch (comdat_reason) { + .none => {}, + .duplicate => err.addNote("COMDAT rule requires no duplicates", .{}), + .size => |s| err.addNote( + "COMDAT rule require duplicates to have the same size ({d} vs {d})", + .{ s.a, s.b }, + ), + .crc => |s| err.addNote( + "COMDAT rule require duplicates to have the same CRC (0x{x} vs 0x{x})", + .{ s.a, s.b }, + ), + } + + return error.LinkFailure; +} + const ArchiveMemberHeader = struct { name: []const u8, size: u34, @@ -4078,10 +4458,10 @@ 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 { - _ = coff; _ = prog_node; - log.debug("prelink()", .{}); + + coff.inputs_complete = true; } pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -4410,17 +4790,16 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { num_unique_references = 1; } - const num_notes = - @min(max_notes, num_unique_references) + - @intFromBool(num_unique_references > max_notes); - - var err = try comp.link_diags.addErrorWithNotes(num_notes); + const num_full_notes = @min(max_notes, num_unique_references); + var err = try comp.link_diags.addErrorWithNotes( + num_full_notes + @intFromBool(num_unique_references > max_notes), + ); const target_sym = target.get(coff); try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.globalName(coff).name.toSlice(coff)}); var prev_loc_si: Symbol.Index = .null; for (undef_indices.items[start_i..][0..@max(1, i - start_i)]) |reference_i| { - if (err.note_slot == num_notes) break; + if (err.note_slot == num_full_notes) break; const loc_si = coff.relocs.items[reference_i].loc; if (loc_si == prev_loc_si) continue; @@ -4537,18 +4916,27 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } if (coff.pending_input) |pending_iami| { - // TODO: Prog node? + const name_slice = pending_iami.member(coff).name.toSlice(coff); + const sub_prog_node = coff.input_prog_node.start( + name_slice, + 0, + ); + defer sub_prog_node.end(); coff.pending_input = null; coff.flushInputMember(pending_iami) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return comp.link_diags.fail( - "linker failed to load archive member: {t}", - .{e}, + "linker failed to load archive member '{f}{f}': {t}", + .{ + pending_iami.member(coff).iai.path(coff), + fmtMemberNameString(name_slice), + e, + }, ), }; break :task; } - if (coff.global_pending_index < coff.globals.count()) { + if (coff.inputs_complete and coff.global_pending_index < coff.globals.count()) { const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index); const sub_prog_node = coff.synth_prog_node.start( gmi.globalName(coff).name.toSlice(coff), @@ -4699,7 +5087,7 @@ 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.globals.count() > coff.global_pending_index) return true; + if (coff.inputs_complete and coff.globals.count() > coff.global_pending_index) 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; @@ -4810,12 +5198,17 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const comp = coff.base.comp; const gpa = comp.gpa; const gn = gmi.globalName(coff); - log.debug("flushGlobal({s}, {?s}) = {d}", .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), gmi.symbol(coff) }); + const si = gmi.symbol(coff); + const sym = si.get(coff); + + log.debug( + "flushGlobal({s}, {?s}) = {d} ({d})", + .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), si, sym.ni }, + ); if (!coff.isImage()) { - const si = gmi.symbol(coff); try coff.pendingSymbolTableEntry(si); - if (coff.isArchive() and si.get(coff).ni != .none) + if (coff.isArchive() and sym.ni != .none) try coff.ensureMemberSymbol( coff.getNode(Node.known.zcu_member).archive_member, gn.name, @@ -4945,8 +5338,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas; }, } - const si = gmi.symbol(coff); - const sym = si.get(coff); sym.section_number = Symbol.Index.text.get(coff).section_number; assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); @@ -4980,14 +5371,49 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); - } else { - if (coff.input_archive_symbol_indices.get(gn.name)) |index| { + } else if (sym.ni == .none) { + switch (sym.value) { + .alias_si => |alias_si| { + assert(sym.section_number == .UNDEFINED); + assert(sym.loc_relocs == .none); + + const alias_sym = alias_si.get(coff); + var ri = sym.target_relocs; + while (ri != .none) { + const reloc = ri.get(coff); + assert(reloc.target == si); + reloc.target = alias_si; + if (reloc.next == .none) { + reloc.next = alias_sym.target_relocs; + if (alias_sym.target_relocs != .none) + alias_sym.target_relocs.get(coff).prev = ri; + } + ri = reloc.next; + } + + sym.target_relocs = .none; + coff.globals.values()[gmi.unwrap().?] = alias_si; + alias_si.applyTargetRelocs(coff); + + log.debug("flushGlobal({s}, {?s}) alias {d}->{d}", .{ + gmi.globalName(coff).name.toSlice(coff), + gmi.globalName(coff).lib_name.toSlice(coff), + si, + alias_si, + }); + + return true; + }, + .size => {}, + .input_offset => unreachable, + } + + if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| { var iter: InputArchive.Member.Symbol.Index = index.first; while (true) { const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; - - // TODO: This implies that loading an input failing is not fatal if (!coff.input_archive_members.items[@intFromEnum(archive_sym.iami)].flags.is_loaded) { + // Try loading the input member and then retry coff.pending_input = archive_sym.iami; return false; } @@ -4996,8 +5422,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { iter = archive_sym.next; } } - - // TODO: Check if we can get flushGlobal before prelink, that would cause a problem } return true; @@ -5116,19 +5540,8 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { ); }, .input_section => |isi| { - const ii = isi.input(coff); isi.symbol(coff).flushMoved(coff); - - { - var si = ii.firstSymbol(coff); - const end_si = ii.endSymbol(coff); - while (@intFromEnum(si) < @intFromEnum(end_si)) : (si = si.next()) { - if (si.get(coff).ni != ni) continue; - si.flushMoved(coff); - } - } - - for (coff.input_resolved.items[@intFromEnum(isi.firstResolvedSymbol(coff))..]) |si| { + for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |si| { if (si.get(coff).ni != ni) break; si.flushMoved(coff); } @@ -5444,7 +5857,7 @@ fn flushExportsSort(coff: *Coff) void { entries: []ExportTable.Entry, nt: []const u8, - pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + pub fn lessThan(ctx: *const @This(), lhs: usize, rhs: usize) bool { const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)]; const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)]; return std.mem.lessThan( @@ -5460,7 +5873,7 @@ fn flushExportsSort(coff: *Coff) void { } }; - std.sort.pdqContext(0, coff.export_table.entries.count(), Context{ + std.sort.pdqContext(0, coff.export_table.entries.count(), &Context{ .coff = coff, .np = coff.exportNamePointerTableSlice(), .ord = coff.exportOrdinalTableSlice(), -- 2.54.0 From 652902ac7f424a719abf89a6532e207d03368728 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:34 -0400 Subject: [PATCH 24/94] Coff: support loading import libraries - Ignore duplicate inputs - Fix handling COMDAT sections with no COMDAT symbol --- src/link/Coff.zig | 529 +++++++++++++++++++++++++++++++--------------- 1 file changed, 362 insertions(+), 167 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index b78eea02dda57effcee48c5ca6538d815cbf4437..e307ef9a8e683869179e181a8738630e42dc08d0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -31,6 +31,7 @@ long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, symbol_table: SymbolTable, +inputs: std.ArrayHashMapUnmanaged(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false), input_archives: std.ArrayList(InputArchive), input_archive_members: std.ArrayList(InputArchive.Member), input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol), @@ -39,7 +40,7 @@ input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct { last: InputArchive.Member.Symbol.Index, }), pending_input: ?InputArchive.Member.Index, -inputs: std.ArrayList(Input), +input_objects: std.ArrayList(InputObject), input_symbols: std.ArrayList(Symbol.Index), input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, @@ -254,6 +255,10 @@ pub const Node = union(enum) { return coff.globals.keys()[gmi.unwrap().?]; } + pub fn globalNameMutable(gmi: GlobalMapIndex, coff: *Coff) *GlobalName { + return &coff.globals.keys()[gmi.unwrap().?]; + } + pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index { return coff.globals.values()[gmi.unwrap().?]; } @@ -283,20 +288,8 @@ pub const Node = union(enum) { } }; - pub const InputIndex = enum(u32) { - _, - - pub fn path(ii: InputIndex, coff: *const Coff) std.Build.Cache.Path { - return coff.inputs.items[@intFromEnum(ii)].path; - } - - pub fn memberName(ii: InputIndex, coff: *const Coff) ?[]const u8 { - return coff.inputs.items[@intFromEnum(ii)].member_name; - } - }; - const InputSection = struct { - ii: Node.InputIndex, + ioi: InputObject.Index, si: Symbol.Index, file_location: MappedFile.Node.FileLocation, first_li: Node.InputSection.LocalIndex, @@ -309,8 +302,8 @@ pub const Node = union(enum) { return &coff.input_sections.items[@intFromEnum(isi)]; } - pub fn input(isi: Index, coff: *const Coff) InputIndex { - return coff.input_sections.items[@intFromEnum(isi)].ii; + pub fn input(isi: Index, coff: *const Coff) InputObject.Index { + return coff.input_sections.items[@intFromEnum(isi)].ioi; } pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation { @@ -409,10 +402,20 @@ pub const InputArchive = struct { pub const Member = struct { iai: InputArchive.Index, name: String, - // This range includes the member header - file_location: MappedFile.Node.FileLocation, + content: union(enum) { + // This range includes the member header + object: MappedFile.Node.FileLocation, + import: struct { + symbol_name: String, + lib_name: String, + // Either ordinal or hint, depending on value of name_type + import_ordinal_hint: u16, + type: std.coff.ImportType, + name_type: std.coff.ImportNameType, + }, + }, flags: packed struct { - is_import: bool, + // Set if an attempt was made to load this member is_loaded: bool, }, @@ -436,10 +439,22 @@ pub const InputArchive = struct { }; }; -pub const Input = struct { +pub const InputObject = struct { path: std.Build.Cache.Path, member_name: ?[]const u8, source_name: String.Optional, + + pub const Index = enum(u32) { + _, + + pub fn path(ioi: Index, coff: *const Coff) std.Build.Cache.Path { + return coff.input_objects.items[@intFromEnum(ioi)].path; + } + + pub fn memberName(ioi: Index, coff: *const Coff) ?[]const u8 { + return coff.input_objects.items[@intFromEnum(ioi)].member_name; + } + }; }; pub const Member = struct { @@ -952,7 +967,7 @@ pub const Symbol = struct { }; comptime { - if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 32); + if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 36); } }; @@ -1381,12 +1396,13 @@ fn create( .pending = .empty, .pending_shrink = false, }, + .inputs = .empty, .input_archives = .empty, .input_archive_members = .empty, .input_archive_symbols = .empty, .input_archive_symbol_indices = .empty, .pending_input = null, - .inputs = .empty, + .input_objects = .empty, .input_symbols = .empty, .input_sections = .empty, .input_section_pending_index = 0, @@ -1447,11 +1463,12 @@ pub fn deinit(coff: *Coff) void { coff.export_table.entries.deinit(gpa); coff.symbol_table.strings.deinit(gpa); coff.symbol_table.pending.deinit(gpa); + coff.inputs.deinit(gpa); coff.input_archives.deinit(gpa); coff.input_archive_members.deinit(gpa); coff.input_archive_symbols.deinit(gpa); coff.input_archive_symbol_indices.deinit(gpa); - coff.inputs.deinit(gpa); + coff.input_objects.deinit(gpa); coff.input_symbols.deinit(gpa); coff.input_sections.deinit(gpa); coff.strings.deinit(gpa); @@ -2772,23 +2789,28 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void { const member = iami.member(coff); - if (member.file_location.size == 0) return; assert(!member.flags.is_loaded); defer member.flags.is_loaded = true; - const comp = coff.base.comp; - const io = comp.io; - const path = member.iai.path(coff); - const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); - defer file.close(io); - var buffer: [4096]u8 = undefined; - var fr = file.reader(io, &buffer); - const offset = member.file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader); - try fr.seekTo(offset); - log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) }); - try coff.loadObject(path, member.name.toSlice(coff), &fr, .{ - .offset = offset, - .size = member.file_location.size, - }); + switch (member.content) { + .import => unreachable, + .object => |file_location| { + if (file_location.size == 0) return; + const comp = coff.base.comp; + const io = comp.io; + const path = member.iai.path(coff); + const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); + defer file.close(io); + var buffer: [4096]u8 = undefined; + var fr = file.reader(io, &buffer); + const offset = file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader); + try fr.seekTo(offset); + log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) }); + try coff.loadObject(path, member.name.toSlice(coff), &fr, .{ + .offset = offset, + .size = file_location.size, + }); + }, + } } fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { @@ -2797,8 +2819,8 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { const comp = coff.base.comp; const io = comp.io; const gpa = comp.gpa; - const ii = isi.input(coff); - const path = ii.path(coff); + const ioi = isi.input(coff); + const path = ioi.path(coff); const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); defer file.close(io); var fr = file.reader(io, &.{}); @@ -2809,7 +2831,7 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { defer nw.deinit(); log.debug("flushInputSection({f}{f}, {s})", .{ path, - fmtMemberNameString(ii.memberName(coff)), + fmtMemberNameString(ioi.memberName(coff)), isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), }); if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) @@ -3216,7 +3238,14 @@ pub fn addReloc( pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError || Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void { - const io = coff.base.comp.io; + const comp = coff.base.comp; + const io = comp.io; + + const path = input.path() orelse unreachable; + const gop = try coff.inputs.getOrPut(comp.gpa, path); + if (gop.found_existing) return; + errdefer _ = coff.inputs.swapRemove(path); + var buf: [4096]u8 = undefined; switch (input) { .object => |object| { @@ -3340,9 +3369,9 @@ fn loadObject( symbol_table_end + string_table_len > fl.size) return diags.failParse(path, "bad string table", .{}); - const ii: Node.InputIndex = @enumFromInt(coff.inputs.items.len); - try coff.inputs.ensureUnusedCapacity(gpa, 1); - const input = coff.inputs.addOneAssumeCapacity(); + const ioi: InputObject.Index = @enumFromInt(coff.input_objects.items.len); + try coff.input_objects.ensureUnusedCapacity(gpa, 1); + const input = coff.input_objects.addOneAssumeCapacity(); input.* = .{ .path = path, .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null, @@ -3743,8 +3772,12 @@ fn loadObject( else => |e| return e, }) |arg| { // Microsoft tools emit 3 space characters into this section even with /Zl - if (arg.len > 0) - return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg}); + if (arg.len == 0) continue; + + if (std.mem.cutPrefix(u8, arg, "-exclude-symbols:")) |rest| { + // TODO: When implementing mingw auto-exports, use this to not export this symbol + _ = rest; + } else return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg}); } } @@ -3784,29 +3817,37 @@ fn loadObject( }; }, else => |comdat| { - const psi = section.comdat_psi.unwrap() orelse - return diags.failParse( - path, - "COMDAT section symbol 0x{x} had no COMDAT symbol", - .{pending_symbols.keys()[section.psi.unwrap().?]}, - ); - + const psi = section.comdat_psi.unwrap() orelse section.psi.unwrap().?; const symbol = &pending_symbols.values()[psi]; - switch (symbol.value) { - .section, .weak_external => unreachable, + const si = existing: switch (symbol.value) { + .weak_external => unreachable, .static => break :comdat .include, - else => {}, - } + .section => { + assert(section.comdat_psi == .none); + if (coff.object_section_table.get(section.name)) |si| + break :existing si + else if (coff.pseudo_section_table.get(section.name)) |si| + break :existing si + else if (coff.section_table.get(section.name)) |s| + break :existing s.si + else + break :comdat .include; + }, + else => { + const global_gop = try coff.getOrPutGlobalSymbol(.{ + .name = symbol.name.toSlice(coff), + .lib_name = null, + }); + if (!global_gop.found_existing) { + symbol.si = global_gop.value_ptr.*; + break :comdat .include; + } - // TODO: Do we need to use lib_name here? - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff), .lib_name = null }); - if (!global_gop.found_existing) { - symbol.si = global_gop.value_ptr.*; - break :comdat .include; - } + break :existing global_gop.value_ptr.*; + }, + }; const index = pending_symbols.keys()[psi]; - const si = global_gop.value_ptr.*; switch (comdat) { .NODUPLICATES => return coff.failMultipleDefinitions( path, @@ -3816,12 +3857,17 @@ fn loadObject( si, .duplicate, ), - .ANY => break :comdat .skip, + .ANY => { + symbol.si = si; + break :comdat .skip; + }, .SAME_SIZE => { // TODO: Verify that this node isn't resized after creation _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf); - if (size == section.header.size_of_raw_data) + if (size == section.header.size_of_raw_data) { + symbol.si = si; break :comdat .skip; + } return coff.failMultipleDefinitions( path, @@ -3840,8 +3886,10 @@ fn loadObject( else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)), }; - if (existing_crc == section.comdat_crc) + if (existing_crc == section.comdat_crc) { + symbol.si = si; break :comdat .skip; + } return coff.failMultipleDefinitions( path, @@ -3876,7 +3924,16 @@ fn loadObject( continue :comdat root_result; }, .include => {}, - .skip => continue, + .skip => { + assert(switch (section.comdat) { + .NONE, .ASSOCIATIVE => true, + else => if (section.comdat_psi.unwrap()) |psi| + pending_symbols.values()[psi].si != .null + else + pending_symbols.values()[section.psi.unwrap().?].si != .null, + }); + continue; + }, .pending => unreachable, } @@ -3915,7 +3972,7 @@ fn loadObject( sym.section_number = section.parent_si.get(coff).section_number; coff.input_sections.addOneAssumeCapacity().* = .{ - .ii = ii, + .ioi = ioi, .si = section.si, .file_location = .{ .offset = fl.offset + section.header.pointer_to_raw_data, @@ -3987,8 +4044,7 @@ fn loadObject( symbol.si = coff.addSymbolAssumeCapacity(); }, .external => { - // COMDAT symbols were created when enumerating the sections - assert(section.comdat == .NONE); + // TODO: Assert this is not the comdat leader const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); symbol.si = global_gop.value_ptr.*; @@ -4086,20 +4142,26 @@ fn loadObject( try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections); var prev_sn: Symbol.SectionNumber = .UNDEFINED; + var include_section = true; for (pending_symbols.values()) |symbol| { // The symbol may have not been included, or it's an undefined external if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue; - assert(coff.getNode(symbol.si.get(coff).ni) == .input_section); if (prev_sn != symbol.section_number) { prev_sn = symbol.section_number; const section = §ions[symbol.section_number.toIndex()]; - const isi = coff.getNode(section.si.get(coff).ni).input_section; - isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len); + include_section = section.comdat_result == .include; + if (include_section) { + const isi = coff.getNode(section.si.get(coff).ni).input_section; + isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len); + } } - coff.input_symbols.addOneAssumeCapacity().* = symbol.si; + if (include_section) { + assert(coff.getNode(symbol.si.get(coff).ni) == .input_section); + coff.input_symbols.addOneAssumeCapacity().* = symbol.si; + } } } @@ -4123,16 +4185,15 @@ fn failMultipleDefinitions( switch (coff.getNode(existing_si.get(coff).ni)) { .input_section => |isi| { - const other_ii = isi.input(coff); + const other_ioi = isi.input(coff); err.addNote("first seen in input '{f}{f}'", .{ - other_ii.path(coff).fmtEscapeString(), - fmtMemberNameString(other_ii.memberName(coff)), + other_ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(other_ioi.memberName(coff)), }); }, .nav, .uav => err.addNote("first seen in module '{s}'", .{ coff.base.comp.zcu.?.root_mod.fully_qualified_name, }), - //else => |_, tag| err.addNote("TODO multiple def for {t}", .{tag}), else => unreachable, } @@ -4158,6 +4219,7 @@ const ArchiveMemberHeader = struct { size: u34, }; +/// Return value lifetime is that of `header` fn parseArchiveMemberHeader( diags: *link.Diags, path: std.Build.Cache.Path, @@ -4346,10 +4408,14 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo coff.input_archive_members.addOneAssumeCapacity().* = .{ .iai = iai, .name = undefined, - .flags = undefined, - .file_location = .{ - .offset = member_offset, - .size = undefined, + .content = .{ + .object = .{ + .offset = member_offset, + .size = undefined, + }, + }, + .flags = .{ + .is_loaded = false, }, }; @@ -4394,9 +4460,9 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo else => {}, }; - // Validate / read names and sizes of all the referenced members + // Validate / read names and sizes of all the referenced members, enumerate imports for (coff.input_archive_members.items[first_iami..]) |*member| { - try fr.seekTo(member.file_location.offset); + try fr.seekTo(member.content.object.offset); const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian); const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames); @@ -4405,28 +4471,74 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo member.name = coff.getOrPutStringAssumeCapacity(res.name); const member_sig = try r.peek(4); - const machine = std.mem.readInt(u16, member_sig[0..2], target_endian); + const machine: std.coff.IMAGE.FILE.MACHINE = + @enumFromInt(std.mem.readInt(u16, member_sig[0..2], target_endian)); const sig = std.mem.readInt(u16, member_sig[2..4], target_endian); - member.flags = .{ - .is_import = machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff, - .is_loaded = false, - }; - member.file_location.size = res.size; log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{ res.name, - member.file_location.offset, - member.file_location.size, + member.content.object.offset, + res.size, }); - if (member.flags.is_import) { - const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian); - // TODO: Validate import table header fields - // TODO: Use this result in flushGlobal - return diags.failParse(path, "TODO implement parsing import headers: {t} {t}", .{ + const expected_machine = comp.root_mod.resolved_target.result.toCoffMachine(); + if (machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff) { + const import_header = try r.takeStruct(std.coff.ImportHeader, target_endian); + const strings = r.take(import_header.size_of_data) catch |err| switch (err) { + error.EndOfStream => return diags.failParse(path, "invalid data size in import header '{s}'", .{res.name}), + else => |e| return e, + }; + + var split = std.mem.splitScalar(u8, strings, 0); + const symbol_name = split.next() orelse + return diags.failParse(path, "invalid symbol name string in import header '{s}'", .{res.name}); + var lib_name = split.next() orelse + return diags.failParse(path, "invalid dll name string in import header '{s}' ('{s}')", .{ res.name, symbol_name }); + + if (import_header.machine != expected_machine) + return diags.failParse(path, "machine mismatch in import header '{s}' ('{s}'): expected {t}, found {t}", .{ + res.name, + symbol_name, + expected_machine, + machine, + }); + + const ext = ".dll"; + if (!std.mem.endsWith(u8, lib_name, ext)) + return diags.failParse( + path, + "unexpected extension for import '{s} ('{s}'): '{s}'", + .{ res.name, symbol_name, lib_name }, + ); + + lib_name = lib_name[0 .. lib_name.len - ext.len]; + log.debug("verifyArchiveImportHeader({s}, {s}, {s}) = {t} ({t})", .{ + res.name, + symbol_name, + lib_name, import_header.types.type, import_header.types.name_type, }); + + try coff.ensureManyUnusedStringCapacity(2, strings.len - ext.len); + member.content = .{ + .import = .{ + .symbol_name = coff.getOrPutStringAssumeCapacity(symbol_name), + .lib_name = coff.getOrPutStringAssumeCapacity(lib_name), + .import_ordinal_hint = import_header.hint, + .type = import_header.types.type, + .name_type = import_header.types.name_type, + }, + }; + } else { + member.content.object.size = res.size; + if (machine != expected_machine) { + return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{ + res.name, + expected_machine, + machine, + }); + } } } } @@ -4808,18 +4920,18 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { const loc_sym = loc_si.get(coff); switch (coff.getNode(loc_sym.ni)) { .input_section => |isi| { - const other_ii = isi.input(coff); + const other_ioi = isi.input(coff); if (loc_sym.gmi == .none) { // TODO: We could report the name here if we interned it in loadObject err.addNote("referenced internally by input '{f}{f}'", .{ - other_ii.path(coff).fmtEscapeString(), - fmtMemberNameString(other_ii.memberName(coff)), + other_ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(other_ioi.memberName(coff)), }); } else { err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{ loc_sym.gmi.globalName(coff).name.toSlice(coff), - other_ii.path(coff).fmtEscapeString(), - fmtMemberNameString(other_ii.memberName(coff)), + other_ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(other_ioi.memberName(coff)), }); } }, @@ -5010,13 +5122,13 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { defer sub_prog_node.end(); coff.flushInputSection(isi) catch |err| switch (err) { else => |e| { - const ii = isi.input(coff); + const ioi = isi.input(coff); return comp.link_diags.fail( "linker failed to read input section '{s}' from \"{f}{f}\": {t}", .{ isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), - ii.path(coff).fmtEscapeString(), - fmtMemberNameString(ii.memberName(coff)), + ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(ioi.memberName(coff)), e, }, ); @@ -5110,10 +5222,10 @@ fn idleProgNode( .image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0), inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff), .input_section => |isi| { - const ii = isi.input(coff); + const ioi = isi.input(coff); break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{ - ii.path(coff).fmtEscapeString(), - fmtMemberNameString(ii.memberName(coff)), + ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(ioi.memberName(coff)), coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }) catch &name; }, @@ -5197,7 +5309,7 @@ fn flushUav( fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const comp = coff.base.comp; const gpa = comp.gpa; - const gn = gmi.globalName(coff); + const gn = gmi.globalNameMutable(coff); const si = gmi.symbol(coff); const sym = si.get(coff); @@ -5217,8 +5329,142 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } - if (gn.lib_name.toSlice(coff)) |lib_name| { - const name = gn.name.toSlice(coff); + const Import = struct { + lib_name: String, + ref: union(enum) { + name: struct { + str: []const u8, + hint: ?u16, + }, + ordinal: u16, + }, + }; + + const opt_import: ?Import = if (gn.lib_name == .none and sym.ni == .none) import: { + switch (sym.value) { + .alias_si => |alias_si| { + assert(sym.section_number == .UNDEFINED); + assert(sym.loc_relocs == .none); + + const alias_sym = alias_si.get(coff); + var ri = sym.target_relocs; + while (ri != .none) { + const reloc = ri.get(coff); + assert(reloc.target == si); + reloc.target = alias_si; + if (reloc.next == .none) { + reloc.next = alias_sym.target_relocs; + if (alias_sym.target_relocs != .none) + alias_sym.target_relocs.get(coff).prev = ri; + } + ri = reloc.next; + } + + sym.target_relocs = .none; + coff.globals.values()[gmi.unwrap().?] = alias_si; + alias_si.applyTargetRelocs(coff); + + log.debug( + "flushGlobal({s}, null) alias {d}->{d}", + .{ gmi.globalName(coff).name.toSlice(coff), si, alias_si }, + ); + return true; + }, + .size => {}, + .input_offset => unreachable, + } + + if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| { + var iter: InputArchive.Member.Symbol.Index = index.first; + while (true) { + const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; + const member = &coff.input_archive_members.items[@intFromEnum(archive_sym.iami)]; + if (!member.flags.is_loaded) { + switch (member.content) { + .import => |import| switch (import.type) { + .CODE, + .DATA, + => { + defer member.flags.is_loaded = true; + // gn.lib_name = import.lib_name.toOptional(); + // try coff.globals.setKey(gpa, gmi.unwrap().?, gn.*); + + // Switch this global to an import + switch (import.name_type) { + .NAME, + .NAME_NOPREFIX, + .NAME_UNDECORATE, + => |tag| { + var name: []const u8 = import.symbol_name.toSlice(coff); + if (!(std.mem.eql(u8, name, gn.name.toSlice(coff)))) + return comp.link_diags.fail("import '{s}' has mismatched symbol name: '{s}'", .{ + import.symbol_name.toSlice(coff), + gn.name.toSlice(coff), + }); + + name = if (tag == .NAME) name else name: { + name = std.mem.trimStart(u8, name, "?@_"); + if (tag == .NAME_UNDECORATE) + name = std.mem.sliceTo(name, '@'); + break :name name; + }; + + break :import .{ + .lib_name = import.lib_name, + .ref = .{ + .name = .{ + .str = name, + .hint = import.import_ordinal_hint, + }, + }, + }; + }, + .ORDINAL => break :import .{ + .lib_name = import.lib_name, + .ref = .{ .ordinal = import.import_ordinal_hint }, + }, + else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}), + } + }, + .CONST => return comp.link_diags.fail("TODO handle import type CONST", .{}), + else => |t| return comp.link_diags.fail("invalid import type: {d}", .{t}), + }, + .object => { + // Try loading the input member and then retry + coff.pending_input = archive_sym.iami; + return false; + }, + } + } + + if (archive_sym.next == iter) break; + iter = archive_sym.next; + } + } + + break :import null; + } else if (gn.lib_name.unwrap()) |lib_name| .{ + .lib_name = lib_name, + .ref = .{ + .name = .{ + .str = gn.name.toSlice(coff), + .hint = null, + }, + }, + } else null; + + if (opt_import) |import| { + assert(sym.ni == .none); + const lib_name = import.lib_name.toSlice(coff); + const name = switch (import.ref) { + .name => |n| n.str, + .ordinal => return comp.link_diags.fail("TODO handle imports via ordinal", .{}), + }; + + log.debug("flushGlobalImport({s}, {s})", .{ name, lib_name }); + + // TODO: Handle hint + try coff.nodes.ensureUnusedCapacity(gpa, 4); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -5371,57 +5617,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); - } else if (sym.ni == .none) { - switch (sym.value) { - .alias_si => |alias_si| { - assert(sym.section_number == .UNDEFINED); - assert(sym.loc_relocs == .none); - - const alias_sym = alias_si.get(coff); - var ri = sym.target_relocs; - while (ri != .none) { - const reloc = ri.get(coff); - assert(reloc.target == si); - reloc.target = alias_si; - if (reloc.next == .none) { - reloc.next = alias_sym.target_relocs; - if (alias_sym.target_relocs != .none) - alias_sym.target_relocs.get(coff).prev = ri; - } - ri = reloc.next; - } - - sym.target_relocs = .none; - coff.globals.values()[gmi.unwrap().?] = alias_si; - alias_si.applyTargetRelocs(coff); - - log.debug("flushGlobal({s}, {?s}) alias {d}->{d}", .{ - gmi.globalName(coff).name.toSlice(coff), - gmi.globalName(coff).lib_name.toSlice(coff), - si, - alias_si, - }); - - return true; - }, - .size => {}, - .input_offset => unreachable, - } - - if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| { - var iter: InputArchive.Member.Symbol.Index = index.first; - while (true) { - const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; - if (!coff.input_archive_members.items[@intFromEnum(archive_sym.iami)].flags.is_loaded) { - // Try loading the input member and then retry - coff.pending_input = archive_sym.iami; - return false; - } - - if (archive_sym.next == iter) break; - iter = archive_sym.next; - } - } } return true; @@ -6097,10 +6292,10 @@ pub fn printNode( std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0), }), .input_section => |isi| { - const ii = isi.input(coff); + const ioi = isi.input(coff); try w.print("({f}{f}, {s})", .{ - ii.path(coff).fmtEscapeString(), - fmtMemberNameString(ii.memberName(coff)), + ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(ioi.memberName(coff)), coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }); }, -- 2.54.0 From a22ca5d4169406576776436a9bec5c500bc3f2d4 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 25/94] Coff: more progress on imports - Support __imp_ prefixed symbols - Support data imports - Support globals symbols pointing directly to IAT entries - Don't create duplicate IAT entries if multiple globals reference the same import name / ordinal - Rework storage of Symbol.value - Add support for `is_dll_import` - Supply host libc libs to the linker - Add std.meta.BareUnion (from multi_array_list) --- lib/std/meta.zig | 9 + lib/std/multi_array_list.zig | 2 +- src/link.zig | 60 +++- src/link/Coff.zig | 629 +++++++++++++++++++++-------------- 4 files changed, 450 insertions(+), 250 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 3de8d0aa9eaebf6fe213d774e8d75e4248e76b3c..f1e59438ec2e418794230585ee2a3bb019a7aa22 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -499,6 +499,15 @@ test DeclEnum { try expectEqualEnum(enum {}, DeclEnum(D)); } +pub fn BareUnion(comptime T: type) type { + const u = switch (@typeInfo(T)) { + .@"union" => |u| u, + else => @compileError("expected union type, found '" ++ @typeName(T) ++ "'"), + }; + + return @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]); +} + pub fn Tag(comptime T: type) type { return switch (@typeInfo(T)) { .@"enum" => |info| info.tag_type, diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig index 7b99eeb63e1185c0466527b42a99770819a28f46..95404a3288e8459ff334c6e0c0558c71d3d563f6 100644 --- a/lib/std/multi_array_list.zig +++ b/lib/std/multi_array_list.zig @@ -44,7 +44,7 @@ pub fn MultiArrayList(comptime T: type) type { const Elem = switch (@typeInfo(T)) { .@"struct" => T, .@"union" => |u| struct { - pub const Bare = @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]); + pub const Bare = std.meta.BareUnion(T); pub const Tag = u.tag_type orelse @compileError("MultiArrayList does not support untagged unions"); tags: Tag, diff --git a/src/link.zig b/src/link.zig index 9cfd1d8a228dff54c1e4651d222acfd26f4863f5..1b7033049f8cfcf21a95671cbc4f732bf339d09d 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1473,7 +1473,8 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { const target = &comp.root_mod.resolved_target.result; const flags = target_util.libcFullLinkFlags(target); - const crt_dir = comp.libc_installation.?.crt_dir.?; + const libc_installation = comp.libc_installation.?; + const crt_dir = libc_installation.crt_dir.?; const sep = std.fs.path.sep_str; for (flags) |flag| { assert(mem.startsWith(u8, flag, "-l")); @@ -1525,6 +1526,63 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }, } } + + if (target.os.tag == .windows) { + const inputs: []const struct { + dir: enum { crt, msvc_lib, kernel32_lib }, + name: []const u8, + } = if (target.abi.isGnu()) switch (comp.config.link_mode) { + .dynamic => &.{ + .{ .dir = .crt, .name = "dllcrt2.obj" }, + .{ .dir = .crt, .name = "libmingw32.lib" }, + }, + .static => &.{ + .{ .dir = .crt, .name = "crt2.obj" }, + .{ .dir = .crt, .name = "libmingw32.lib" }, + }, + } else switch (comp.config.link_mode) { + .dynamic => &.{ + .{ .dir = .msvc_lib, .name = "msvcrt.lib" }, + .{ .dir = .msvc_lib, .name = "vcruntime.lib" }, + .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" }, + .{ .dir = .crt, .name = "ucrt.lib" }, + .{ .dir = .kernel32_lib, .name = "kernel32.lib" }, + .{ .dir = .kernel32_lib, .name = "ntdll.lib" }, + }, + .static => &.{ + .{ .dir = .msvc_lib, .name = "libcmt.lib" }, + .{ .dir = .msvc_lib, .name = "libvcruntime.lib" }, + .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" }, + .{ .dir = .crt, .name = "libucrt.lib" }, + .{ .dir = .kernel32_lib, .name = "kernel32.lib" }, + .{ .dir = .kernel32_lib, .name = "ntdll.lib" }, + }, + }; + + for (inputs) |lib| { + const path = Path.initCwd( + std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}", .{ + switch (lib.dir) { + .crt => crt_dir, + .msvc_lib => libc_installation.msvc_lib_dir.?, + .kernel32_lib => libc_installation.kernel32_lib_dir.?, + }, + lib.name, + }) catch return diags.setAllocFailure(), + ); + if (std.mem.endsWith(u8, lib.name, "lib")) { + base.openLoadArchive(path, null) catch |err| switch (err) { + error.LinkFailure => return, // error reported via diags + else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), + }; + } else { + base.openLoadObject(path) catch |err| switch (err) { + error.LinkFailure => return, // error reported via diags + else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}), + }; + } + } + } }, .load_object => |path| { const prog_node = comp.link_prog_node.start("Parse Object", 0); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e307ef9a8e683869179e181a8738630e42dc08d0..08684a57226a3300c233d241fad18c0f9777ab60 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -84,6 +84,8 @@ pub const default_size_of_heap_commit: u32 = 0x1000; pub const archive_signature = "!\n"; pub const archive_end_of_header = "`\n"; +pub const imp_prefix = "__imp_"; + /// This is the start of a Portable Executable (PE) file. /// It starts with a MS-DOS header followed by a MS-DOS stub program. /// This data does not change so we include it as follows in all binaries. @@ -203,7 +205,7 @@ pub const Node = union(enum) { pseudo_section: PseudoSectionMapIndex, object_section: ObjectSectionMapIndex, input_section: InputSection.Index, - global: GlobalMapIndex, + import_thunk: GlobalMapIndex, // TODO: Rename to import_thunk nav: NavMapIndex, uav: UavMapIndex, lazy_code: LazyMapRef.Index(.code), @@ -255,10 +257,6 @@ pub const Node = union(enum) { return coff.globals.keys()[gmi.unwrap().?]; } - pub fn globalNameMutable(gmi: GlobalMapIndex, coff: *Coff) *GlobalName { - return &coff.globals.keys()[gmi.unwrap().?]; - } - pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index { return coff.globals.values()[gmi.unwrap().?]; } @@ -707,6 +705,12 @@ pub const ExportTable = struct { pub const ImportTable = struct { ni: MappedFile.Node.Index, entries: std.array_hash_map.Auto(void, Entry), + iat_symbol_indices: std.AutoArrayHashMapUnmanaged(struct { + iti: ImportTable.Index, + name: String.Optional, + // If name == .none this is the ordinal, otherwise the hint + ordinal_hint: u16, + }, u32), pub const Entry = struct { import_lookup_table_ni: MappedFile.Node.Index, @@ -815,17 +819,20 @@ pub const Section = struct { pub const GlobalName = struct { name: String, lib_name: String.Optional }; +pub const DllStorageClass = enum(u2) { + default, + dllimport, + dllexport, +}; + pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, - value: union(enum) { - /// For .ni == .input_section, this is the offset of this symbol within the input section - input_offset: u32, - /// For .ni == none and .gmi != .none, this is a weak alias - /// that should replace this symbol, or .null if none exists - alias_si: Symbol.Index, - /// Otherwise, this is the symbol size if known - size: u32, + value: std.meta.BareUnion(Symbol.Value), + flags: packed struct(u16) { + value_tag: ValueTag, + dll_storage_class: DllStorageClass, + _: u12 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -836,6 +843,55 @@ pub const Symbol = struct { sti: SymbolTable.Index, gmi: Node.GlobalMapIndex, + const ValueTag = enum(u2) { + node_offset, + alias_si, + size, + }; + + pub const Value = union(ValueTag) { + /// The offset of the symbol within it's node + node_offset: u32, + /// For undefined globals, this is a weak alias + /// that can replace this symbol, or .null if none exists + alias_si: Symbol.Index, + /// The symbol size, or 0 if unknown + size: u32, + }; + + pub fn setValue(sym: *Symbol, value: Symbol.Value) void { + sym.flags.value_tag = std.meta.activeTag(value); + sym.value = switch (sym.flags.value_tag) { + inline else => |t| @unionInit( + @FieldType(Symbol, "value"), + @tagName(t), + @field(value, @tagName(t)), + ), + }; + } + + pub fn nodeOffset(sym: *const Symbol, coff: *Coff) u32 { + return switch (sym.flags.value_tag) { + .node_offset => offset: { + assert(switch (coff.getNode(sym.ni)) { + // Separate nodes are not created for these entries per-symbol + .input_section, .import_address_table => true, + else => false, + }); + break :offset sym.value.node_offset; + }, + else => 0, + }; + } + + pub fn weakAlias(sym: *const Symbol) Symbol.Index { + return if (sym.flags.value_tag == .alias_si) sym.value.alias_si else .null; + } + + pub fn size(sym: *const Symbol) u32 { + return if (sym.flags.value_tag == .size) sym.value.size else 0; + } + pub const SectionNumber = enum(i16) { UNDEFINED = 0, ABSOLUTE = -1, @@ -847,10 +903,7 @@ pub const Symbol = struct { } fn hasIndex(sn: SectionNumber) bool { - return switch (sn) { - .UNDEFINED, .ABSOLUTE, .DEBUG => false, - else => true, - }; + return @intFromEnum(sn) > 0; } pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index { @@ -902,11 +955,7 @@ pub const Symbol = struct { pub fn flushMoved(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); - sym.rva = coff.computeNodeRva(sym.ni); - if (sym.gmi != .none and coff.getNode(sym.ni) == .input_section) { - // Symbols in input sections share a ni with their section - sym.rva += sym.value.input_offset; - } + sym.rva = coff.computeNodeRva(sym.ni) + sym.nodeOffset(coff); si.applyLocationRelocs(coff); si.applyTargetRelocs(coff); } @@ -967,7 +1016,7 @@ pub const Symbol = struct { }; comptime { - if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 36); + if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 32); } }; @@ -1379,6 +1428,7 @@ fn create( .import_table = .{ .ni = .none, .entries = .empty, + .iat_symbol_indices = .empty, }, .export_table = .{ .ni = .none, @@ -1460,6 +1510,7 @@ pub fn deinit(coff: *Coff) void { coff.lib_string_table.deinit(gpa); coff.long_names_table.entries.deinit(gpa); coff.import_table.entries.deinit(gpa); + coff.import_table.iat_symbol_indices.deinit(gpa); coff.export_table.entries.deinit(gpa); coff.symbol_table.strings.deinit(gpa); coff.symbol_table.pending.deinit(gpa); @@ -1840,16 +1891,7 @@ fn initHeaders( } try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); - coff.symbols.addOneAssumeCapacity().* = .{ - .ni = .none, - .rva = 0, - .value = .{ .size = 0 }, - .loc_relocs = .none, - .target_relocs = .none, - .section_number = .UNDEFINED, - .sti = .none, - .gmi = .none, - }; + assert(coff.addSymbolAssumeCapacity() == .null); assert(try coff.addSection(.@".data", .{ .CNT_INITIALIZED_DATA = true, .MEM_READ = true, @@ -2057,7 +2099,7 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { ), inline .pseudo_section, .object_section, - .global, + .import_thunk, .nav, .uav, .lazy_code, @@ -2070,10 +2112,7 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { return @intCast(parent_rva + offset); } fn computeSymbolSectionOffset(coff: *Coff, sym: *const Symbol) u32 { - var section_offset: u32 = if (sym.gmi != .none and coff.getNode(sym.ni) == .input_section) - sym.value.input_offset - else - 0; + var section_offset: u32 = sym.nodeOffset(coff); var parent_ni = sym.ni; while (true) { const offset, _ = parent_ni.location(&coff.mf).resolve(&coff.mf); @@ -2273,6 +2312,10 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .ni = .none, .rva = 0, .value = .{ .size = 0 }, + .flags = .{ + .value_tag = .size, + .dll_storage_class = .default, + }, .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, @@ -2357,8 +2400,9 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { } const GlobalOptions = struct { - name: []const u8, // TODO: Union with String + name: []const u8, lib_name: ?[]const u8 = null, + dll_storage_class: DllStorageClass = .default, }; fn getOrPutGlobalSymbol( @@ -2373,7 +2417,10 @@ fn getOrPutGlobalSymbol( }); if (!sym_gop.found_existing) { const si = coff.addSymbolAssumeCapacity(); - si.get(coff).gmi = .wrap(@intCast(sym_gop.index)); + const sym = si.get(coff); + sym.setValue(.{ .alias_si = .null }); + sym.gmi = .wrap(@intCast(sym_gop.index)); + sym.flags.dll_storage_class = opts.dll_storage_class; sym_gop.value_ptr.* = si; coff.synth_prog_node.increaseEstimatedTotalItems(1); @@ -2446,6 +2493,7 @@ pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbo if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{ .name = @"extern".name.toSlice(ip), .lib_name = @"extern".lib_name.toSlice(ip), + .dll_storage_class = if (@"extern".is_dll_import) .dllimport else .default, }); const nmi = try coff.navMapIndex(zcu, nav_index); return nmi.symbol(coff); @@ -2774,7 +2822,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }; coff.targetStore(&entry.value, switch (sym.section_number) { - .UNDEFINED => sym.value.size, + .UNDEFINED => sym.size(), .ABSOLUTE, .DEBUG, => unreachable, @@ -2784,7 +2832,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }, }); - log.debug("updateSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); + log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); } fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void { @@ -3838,7 +3886,10 @@ fn loadObject( .name = symbol.name.toSlice(coff), .lib_name = null, }); - if (!global_gop.found_existing) { + + // TODO: What if the same symbol defined twice in this obj? + // TODO: Would need to mark this global as pending, or notice it later when .ni != none + if (!global_gop.found_existing or global_gop.value_ptr.get(coff).ni == .none) { symbol.si = global_gop.value_ptr.*; break :comdat .include; } @@ -4003,9 +4054,7 @@ fn loadObject( if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { const sym = symbol.si.get(coff); if (tag == .external) { - // TOOD: Is it valid to encounter multiple external definitions with different sizes? - assert(sym.value == .size); - sym.value = .{ .size = @max(sym.value.size, value) }; + sym.setValue(.{ .size = @max(sym.size(), value) }); } else { const alias = pending_symbols.getPtr(value) orelse return diags.failParse( @@ -4022,7 +4071,7 @@ fn loadObject( if (alias.si == .null) { alias.weak_external_psi = .wrap(@intCast(psi)); } else { - sym.value = .{ .alias_si = alias.si }; + sym.setValue(.{ .alias_si = alias.si }); } } } @@ -4063,20 +4112,21 @@ fn loadObject( if (section.si != symbol.si) { const sym = symbol.si.get(coff); + assert(sym.ni == .none); sym.ni = section.si.get(coff).ni; - sym.value = switch (symbol.value) { + sym.setValue(switch (symbol.value) { .section => |v| .{ .size = v }, - .static => |v| .{ .input_offset = v }, + .static => |v| .{ .node_offset = v }, .external => |v| switch (symbol.section_number) { .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable, - else => .{ .input_offset = v }, + else => .{ .node_offset = v }, }, .weak_external => unreachable, - }; + }); sym.section_number = symbol.section_number; } - defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}) = {d}@{d}", .{ + log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}) = {d}@{d}", .{ symbol.name.toSlice(coff), index, symbol.value, @@ -4141,20 +4191,21 @@ fn loadObject( pending_symbols.sortUnstable(SortContext{ .v = pending_symbols.values() }); try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections); - var prev_sn: Symbol.SectionNumber = .UNDEFINED; - var include_section = true; + var prev_sn: Symbol.SectionNumber = .DEBUG; + var include_section = false; for (pending_symbols.values()) |symbol| { // The symbol may have not been included, or it's an undefined external if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue; if (prev_sn != symbol.section_number) { prev_sn = symbol.section_number; - - const section = §ions[symbol.section_number.toIndex()]; - include_section = section.comdat_result == .include; - if (include_section) { - const isi = coff.getNode(section.si.get(coff).ni).input_section; - isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len); + if (symbol.section_number.hasIndex()) { + const section = §ions[symbol.section_number.toIndex()]; + include_section = section.comdat_result == .include; + if (include_section) { + const isi = coff.getNode(section.si.get(coff).ni).input_section; + isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len); + } } } @@ -4329,7 +4380,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo var pos = fr.logicalPos(); const size = try fr.getSize(); while (pos < size) : (pos = fr.logicalPos()) { - if ((pos & 1) != 0) r.toss(1); + if ((pos & 1) != 0) try r.discardAll(1); const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian); const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames); @@ -4354,7 +4405,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo const num_members = try r.takeInt(u32, target_endian); pos = fr.logicalPos(); - if (pos + num_members * 4 > member_end) + if (pos + num_members * @sizeOf(u32) > member_end) return diags.failParse(path, "invalid member count 0x{x} in second linker member", .{num_members}); try members.ensureTotalCapacity(gpa, num_members); @@ -4366,7 +4417,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo const num_symbols = try r.takeInt(u32, target_endian); pos = fr.logicalPos(); - if (pos + num_symbols * 2 > member_end) + if (pos + num_symbols * @sizeOf(u16) > member_end) return diags.failParse(path, "invalid symbol count 0x{x} in second linker member", .{num_symbols}); try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); @@ -4532,7 +4583,9 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo }; } else { member.content.object.size = res.size; - if (machine != expected_machine) { + // TODO: If .UNKNOWN assert later that it contains no non-undef symbols? + // Microsoft's CRT contains members that set .UNKNOWN but do have symbols + if (machine != expected_machine and machine != .UNKNOWN) { return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{ res.name, expected_machine, @@ -4935,9 +4988,10 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { }); } }, - .global => |gmi| err.addNote("referenced by '{s}' in module '{s}'", .{ + .import_thunk => |gmi| err.addNote("referenced by import thunk for '{s}'", .{ gmi.globalName(coff).name.toSlice(coff), - comp.zcu.?.root_mod.fully_qualified_name, + // TODO: This won't always have a ZCU + //comp.zcu.?.root_mod.fully_qualified_name, }), inline .nav, .uav, @@ -5099,7 +5153,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (sym.ni != .none) coff.getNode(sym.ni) else - .{ .global = pending_si.key.get(coff).gmi }, + .{ .import_thunk = pending_si.key.get(coff).gmi }, ); defer sub_prog_node.end(); coff.flushSymbolTableEntry( @@ -5229,7 +5283,7 @@ fn idleProgNode( coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }) catch &name; }, - .global => |gmi| gmi.globalName(coff).name.toSlice(coff), + .import_thunk => |gmi| gmi.globalName(coff).name.toSlice(coff), .nav => |nmi| { const ip = &coff.base.comp.zcu.?.intern_pool; break :name ip.getNav(nmi.navIndex(coff)).fqn.toSlice(ip); @@ -5306,10 +5360,45 @@ fn flushUav( si.applyLocationRelocs(coff); } +fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !void { + const gn = gmi.globalName(coff); + const si = gmi.symbol(coff); + const sym = si.get(coff); + assert(sym.section_number == .UNDEFINED); + assert(sym.loc_relocs == .none); + + const alias_sym = alias_si.get(coff); + var ri = sym.target_relocs; + while (ri != .none) { + const reloc = ri.get(coff); + assert(reloc.target == si); + reloc.target = alias_si; + if (reloc.next == .none) { + reloc.next = alias_sym.target_relocs; + if (alias_sym.target_relocs != .none) + alias_sym.target_relocs.get(coff).prev = ri; + } + ri = reloc.next; + } + + sym.target_relocs = .none; + sym.gmi = alias_sym.gmi; + coff.globals.values()[gmi.unwrap().?] = alias_si; + alias_si.applyTargetRelocs(coff); + + log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{ + gn.name.toSlice(coff), + gn.lib_name.toSlice(coff), + si, + alias_si, + if (alias_sym.gmi != .none) alias_sym.gmi.globalName(coff).name.toSlice(coff) else null, + }); +} + fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const comp = coff.base.comp; const gpa = comp.gpa; - const gn = gmi.globalNameMutable(coff); + const gn = gmi.globalName(coff); const si = gmi.symbol(coff); const sym = si.get(coff); @@ -5329,112 +5418,102 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } + { + // Resolve unresolved .WEAK_EXTERNAL symbols to their aliases + const alias_si = sym.weakAlias(); + if (alias_si != .null) { + try coff.aliasGlobal(gmi, alias_si); + return true; + } + } + const Import = struct { lib_name: String, - ref: union(enum) { - name: struct { - str: []const u8, - hint: ?u16, - }, - ordinal: u16, + name: String.Optional, + ordinal_hint: u16, + kind: enum { + iat_ptr, + thunk, }, }; - const opt_import: ?Import = if (gn.lib_name == .none and sym.ni == .none) import: { - switch (sym.value) { - .alias_si => |alias_si| { - assert(sym.section_number == .UNDEFINED); - assert(sym.loc_relocs == .none); + const opt_import: ?Import = if (sym.ni == .none) import: { + const global_name = gn.name.toSlice(coff); + const imp_match = std.mem.startsWith(u8, global_name, imp_prefix); - const alias_sym = alias_si.get(coff); - var ri = sym.target_relocs; - while (ri != .none) { - const reloc = ri.get(coff); - assert(reloc.target == si); - reloc.target = alias_si; - if (reloc.next == .none) { - reloc.next = alias_sym.target_relocs; - if (alias_sym.target_relocs != .none) - alias_sym.target_relocs.get(coff).prev = ri; - } - ri = reloc.next; - } + // Globals may have the __imp_ prefix already if they are undef externals from another input. + const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport) + .{ gn.name, imp_match } + else name: { + try coff.ensureUnusedStringCapacity(imp_prefix.len + global_name.len); + const name = try std.fmt.allocPrint(gpa, imp_prefix ++ "{s}", .{global_name}); + defer gpa.free(name); + break :name .{ coff.getOrPutStringAssumeCapacity(name), true }; + }; - sym.target_relocs = .none; - coff.globals.values()[gmi.unwrap().?] = alias_si; - alias_si.applyTargetRelocs(coff); - - log.debug( - "flushGlobal({s}, null) alias {d}->{d}", - .{ gmi.globalName(coff).name.toSlice(coff), si, alias_si }, - ); - return true; - }, - .size => {}, - .input_offset => unreachable, - } - - if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| { - var iter: InputArchive.Member.Symbol.Index = index.first; + if (coff.input_archive_symbol_indices.get(search_name)) |indices_list| { + var iter: InputArchive.Member.Symbol.Index = indices_list.first; while (true) { const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; const member = &coff.input_archive_members.items[@intFromEnum(archive_sym.iami)]; - if (!member.flags.is_loaded) { - switch (member.content) { - .import => |import| switch (import.type) { - .CODE, - .DATA, - => { - defer member.flags.is_loaded = true; - // gn.lib_name = import.lib_name.toOptional(); - // try coff.globals.setKey(gpa, gmi.unwrap().?, gn.*); + member: switch (member.content) { + .object => if (!member.flags.is_loaded) { + if (gn.lib_name.unwrap()) |lib_name| + if (!std.mem.eql(u8, lib_name.toSlice(coff), member.iai.path(coff).stem())) + break :member; - // Switch this global to an import - switch (import.name_type) { - .NAME, - .NAME_NOPREFIX, - .NAME_UNDECORATE, - => |tag| { - var name: []const u8 = import.symbol_name.toSlice(coff); - if (!(std.mem.eql(u8, name, gn.name.toSlice(coff)))) - return comp.link_diags.fail("import '{s}' has mismatched symbol name: '{s}'", .{ - import.symbol_name.toSlice(coff), - gn.name.toSlice(coff), - }); + // Try loading the input member and then retry. + // This could still be a member containing imports + // that use the older non-IMPORT_HEADER method. + coff.pending_input = archive_sym.iami; + return false; + }, + .import => |import| { + if (gn.lib_name.unwrap()) |lib_name| + if (import.lib_name != lib_name) + break :member; - name = if (tag == .NAME) name else name: { - name = std.mem.trimStart(u8, name, "?@_"); - if (tag == .NAME_UNDECORATE) - name = std.mem.sliceTo(name, '@'); - break :name name; - }; + const name: String.Optional = name: switch (import.name_type) { + .NAME, + .NAME_NOPREFIX, + .NAME_UNDECORATE, + => |tag| { + const symbol_name: []const u8 = import.symbol_name.toSlice(coff); + const end_match = std.mem.endsWith(u8, global_name, symbol_name); + const len_delta = global_name.len -% symbol_name.len; + if (!end_match or + (!imp_match and len_delta != 0) or + (imp_match and len_delta != imp_prefix.len)) + return comp.link_diags.fail( + "global '{s}' has mismatched symbol name in import header: '{s}'", + .{ + gn.name.toSlice(coff), + import.symbol_name.toSlice(coff), + }, + ); - break :import .{ - .lib_name = import.lib_name, - .ref = .{ - .name = .{ - .str = name, - .hint = import.import_ordinal_hint, - }, - }, - }; - }, - .ORDINAL => break :import .{ - .lib_name = import.lib_name, - .ref = .{ .ordinal = import.import_ordinal_hint }, - }, - else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}), - } + const name = if (tag == .NAME) import.symbol_name else undecorated: { + var imp_name = std.mem.trimStart(u8, symbol_name, "?@_"); + if (tag == .NAME_UNDECORATE) + imp_name = std.mem.sliceTo(imp_name, '@'); + + try coff.ensureUnusedStringCapacity(imp_name.len); + break :undecorated coff.getOrPutStringAssumeCapacity(imp_name); + }; + + break :name name.toOptional(); }, - .CONST => return comp.link_diags.fail("TODO handle import type CONST", .{}), - else => |t| return comp.link_diags.fail("invalid import type: {d}", .{t}), - }, - .object => { - // Try loading the input member and then retry - coff.pending_input = archive_sym.iami; - return false; - }, - } + .ORDINAL => break :name .none, + else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}), + }; + + break :import .{ + .lib_name = import.lib_name, + .name = name, + .ordinal_hint = import.import_ordinal_hint, + .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr, + }; + }, } if (archive_sym.next == iter) break; @@ -5442,28 +5521,20 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { } } - break :import null; - } else if (gn.lib_name.unwrap()) |lib_name| .{ - .lib_name = lib_name, - .ref = .{ - .name = .{ - .str = gn.name.toSlice(coff), - .hint = null, - }, - }, + // Allow importing symbols with no implib entry, if a lib_name was specified. + // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification, + // which are not in the implib. + break :import if (gn.lib_name.unwrap()) |lib_name| .{ + .lib_name = lib_name, + .name = gn.name.toOptional(), + .ordinal_hint = 0, + .kind = .iat_ptr, + } else null; } else null; if (opt_import) |import| { assert(sym.ni == .none); const lib_name = import.lib_name.toSlice(coff); - const name = switch (import.ref) { - .name => |n| n.str, - .ordinal => return comp.link_diags.fail("TODO handle imports via ordinal", .{}), - }; - - log.debug("flushGlobalImport({s}, {s})", .{ name, lib_name }); - - // TODO: Handle hint try coff.nodes.ensureUnusedCapacity(gpa, 4); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -5547,76 +5618,138 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { if (target_endian != native_endian) std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries); } - const import_symbol_index = gop.value_ptr.len; - gop.value_ptr.len = import_symbol_index + 1; - const new_symbol_table_size = addr_size * (import_symbol_index + 2); - 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), + + log.debug( + "flushGlobalImport({s}, {?s}, {d}, {s})", + .{ gn.name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name }, ); - 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_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf); - const import_address_slice = import_address_table_ni.slice(&coff.mf); - const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf); - @memset(import_hint_name_slice[import_hint_name_index..][0..2], 0); - @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..name.len], name); - @memset(import_hint_name_slice[import_hint_name_index + 2 + name.len ..], 0); - const import_hint_name_rva = - coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index; - switch (magic) { - _ => unreachable, - inline .PE32, .@"PE32+" => |ct_magic| { - const Addr = switch (ct_magic) { - _ => comptime unreachable, - .PE32 => u32, - .@"PE32+" => u64, - }; - const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice)); - const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice)); - const import_hint_name_rvas: [2]Addr = .{ - std.mem.nativeTo(Addr, @intCast(import_hint_name_rva), target_endian), - std.mem.nativeTo(Addr, 0, target_endian), - }; - import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas; - import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas; - }, - } - sym.section_number = Symbol.Index.text.get(coff).section_number; - assert(sym.loc_relocs == .none); - sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - switch (coff.targetLoad(&coff.headerPtr().machine)) { - else => |tag| @panic(@tagName(tag)), - .AMD64 => { - const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 }; - const target = &comp.root_mod.resolved_target.result; - const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{ - .alignment = switch (comp.root_mod.optimize_mode) { - .Debug, - .ReleaseSafe, - .ReleaseFast, - => target_util.defaultFunctionAlignment(target), - .ReleaseSmall => target_util.minFunctionAlignment(target), - }.toStdMem(), - .size = init.len, - }); - @memcpy(ni.slice(&coff.mf)[0..init.len], &init); - sym.ni = ni; - sym.value.size = init.len; - try coff.addReloc( - si, - init.len - 4, - gop.value_ptr.import_address_table_si, - .{ .known = @intCast(addr_size * import_symbol_index) }, - .{ .AMD64 = .REL32 }, + + const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{ + .iti = @enumFromInt(gop.index), + .name = import.name, + .ordinal_hint = import.ordinal_hint, + }); + if (!iat_symbol_gop.found_existing) { + const import_symbol_index = gop.value_ptr.len; + iat_symbol_gop.value_ptr.* = import_symbol_index; + + gop.value_ptr.len = import_symbol_index + 1; + const new_symbol_table_size = addr_size * (import_symbol_index + 2); + + 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), ); + 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])); + ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian); + @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_name.?.len], opt_name.?); + @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_name.?.len ..], 0); + break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index; + } else 0; + + const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf); + const import_address_slice = import_address_table_ni.slice(&coff.mf); + switch (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 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 = .{ + .{ + .payload = if (import.name == .none) + .{ .ordinal = .{ .ordinal = import.ordinal_hint } } + else + .{ .hint_name_rva = @intCast(import_hint_name_rva) }, + .is_ordinal = import.name == .none, + }, + @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)), + }; + if (native_endian != target_endian) + for (import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v); + + import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas; + import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas; + }, + } + } + + assert(sym.loc_relocs == .none); + const iat_offset: u32 = @intCast(addr_size * iat_symbol_gop.value_ptr.*); + switch (import.kind) { + .iat_ptr => { + const iat_sym = gop.value_ptr.import_address_table_si.get(coff); + sym.section_number = iat_sym.section_number; + sym.ni = iat_sym.ni; + sym.setValue(.{ .node_offset = iat_offset }); + si.flushMoved(coff); + }, + .thunk => { + sym.section_number = Symbol.Index.text.get(coff).section_number; + sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + switch (coff.targetLoad(&coff.headerPtr().machine)) { + else => |tag| @panic(@tagName(tag)), + .AMD64 => { + const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 }; + const target = &comp.root_mod.resolved_target.result; + const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{ + .alignment = switch (comp.root_mod.optimize_mode) { + .Debug, + .ReleaseSafe, + .ReleaseFast, + => target_util.defaultFunctionAlignment(target), + .ReleaseSmall => target_util.minFunctionAlignment(target), + }.toStdMem(), + .size = init.len, + }); + @memcpy(ni.slice(&coff.mf)[0..init.len], &init); + sym.ni = ni; + sym.setValue(.{ .size = init.len }); + try coff.addReloc( + si, + init.len - 4, + gop.value_ptr.import_address_table_si, + .{ .known = iat_offset }, + .{ .AMD64 = .REL32 }, + ); + }, + } + coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi }); + sym.rva = coff.computeNodeRva(sym.ni); + si.applyLocationRelocs(coff); }, } - coff.nodes.appendAssumeCapacity(.{ .global = gmi }); - sym.rva = coff.computeNodeRva(sym.ni); - si.applyLocationRelocs(coff); } return true; @@ -5845,7 +5978,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { }, inline .pseudo_section, .object_section, - .global, + .import_thunk, .nav, .uav, .lazy_code, @@ -5980,7 +6113,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { smi.symbol(coff).get(coff).value.size = @intCast(size); }, - .global, + .import_thunk, .nav, .uav, .lazy_code, @@ -6148,7 +6281,7 @@ fn updateExportsInner( const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; - export_sym.value.size = exported_sym.value.size; + export_sym.setValue(.{ .size = exported_sym.value.size }); export_sym.section_number = exported_sym.section_number; defer export_si.applyTargetRelocs(coff); @@ -6308,7 +6441,7 @@ pub fn printNode( inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{ smi.name(coff).toSlice(coff), }), - .global => |gmi| { + .import_thunk => |gmi| { const gn = gmi.globalName(coff); try w.writeByte('('); if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); -- 2.54.0 From 2be291c98773e25d4a47e5c35744084627d11f15 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 26/94] Coff: fixup extern code imports - Add a type flag to Symbol so that we can select between a thunk or IAT ptr - Library name comparisons are now case insensitive --- src/link/Coff.zig | 60 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 08684a57226a3300c233d241fad18c0f9777ab60..8ffda7c95ad603a3cff51b78b3cdb3040b06c656 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -819,20 +819,15 @@ pub const Section = struct { pub const GlobalName = struct { name: String, lib_name: String.Optional }; -pub const DllStorageClass = enum(u2) { - default, - dllimport, - dllexport, -}; - pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, value: std.meta.BareUnion(Symbol.Value), flags: packed struct(u16) { value_tag: ValueTag, + type: Symbol.Type, dll_storage_class: DllStorageClass, - _: u12 = 0, + _: u10 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -843,6 +838,18 @@ pub const Symbol = struct { sti: SymbolTable.Index, gmi: Node.GlobalMapIndex, + pub const DllStorageClass = enum(u2) { + default, + dllimport, + dllexport, + }; + + pub const Type = enum(u2) { + unknown, + code, + data, + }; + const ValueTag = enum(u2) { node_offset, alias_si, @@ -2314,6 +2321,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .value = .{ .size = 0 }, .flags = .{ .value_tag = .size, + .type = .unknown, .dll_storage_class = .default, }, .loc_relocs = .none, @@ -2401,8 +2409,9 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { const GlobalOptions = struct { name: []const u8, + type: Symbol.Type = .unknown, lib_name: ?[]const u8 = null, - dll_storage_class: DllStorageClass = .default, + dll_storage_class: Symbol.DllStorageClass = .default, }; fn getOrPutGlobalSymbol( @@ -2420,6 +2429,7 @@ fn getOrPutGlobalSymbol( const sym = si.get(coff); sym.setValue(.{ .alias_si = .null }); sym.gmi = .wrap(@intCast(sym_gop.index)); + sym.flags.type = opts.type; sym.flags.dll_storage_class = opts.dll_storage_class; sym_gop.value_ptr.* = si; coff.synth_prog_node.increaseEstimatedTotalItems(1); @@ -2493,6 +2503,8 @@ pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbo if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{ .name = @"extern".name.toSlice(ip), .lib_name = @"extern".lib_name.toSlice(ip), + // TODO: Threadlocal as well? + .type = if (ip.isFunctionType(nav.resolved.?.type)) .code else .data, .dll_storage_class = if (@"extern".is_dll_import) .dllimport else .default, }); const nmi = try coff.navMapIndex(zcu, nav_index); @@ -3881,7 +3893,7 @@ fn loadObject( else break :comdat .include; }, - else => { + .external => { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff), .lib_name = null, @@ -5451,6 +5463,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { break :name .{ coff.getOrPutStringAssumeCapacity(name), true }; }; + // TODO: Try to search for __imp_ even when !is_imp, we want to not use thunks if we can + if (coff.input_archive_symbol_indices.get(search_name)) |indices_list| { var iter: InputArchive.Member.Symbol.Index = indices_list.first; while (true) { @@ -5459,8 +5473,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { member: switch (member.content) { .object => if (!member.flags.is_loaded) { if (gn.lib_name.unwrap()) |lib_name| - if (!std.mem.eql(u8, lib_name.toSlice(coff), member.iai.path(coff).stem())) - break :member; + if (!std.ascii.eqlIgnoreCase( + lib_name.toSlice(coff), + member.iai.path(coff).stem(), + )) break :member; // Try loading the input member and then retry. // This could still be a member containing imports @@ -5470,8 +5486,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }, .import => |import| { if (gn.lib_name.unwrap()) |lib_name| - if (import.lib_name != lib_name) - break :member; + if (!std.ascii.eqlIgnoreCase( + import.lib_name.toSlice(coff), + lib_name.toSlice(coff), + )) break :member; const name: String.Optional = name: switch (import.name_type) { .NAME, @@ -5524,12 +5542,16 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { // Allow importing symbols with no implib entry, if a lib_name was specified. // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification, // which are not in the implib. - break :import if (gn.lib_name.unwrap()) |lib_name| .{ - .lib_name = lib_name, - .name = gn.name.toOptional(), - .ordinal_hint = 0, - .kind = .iat_ptr, - } else null; + if (sym.flags.type != .unknown) { + if (gn.lib_name.unwrap()) |lib_name| break :import .{ + .lib_name = lib_name, + .name = gn.name.toOptional(), + .ordinal_hint = 0, + .kind = if (sym.flags.type == .code) .thunk else .iat_ptr, + }; + } + + break :import null; } else null; if (opt_import) |import| { -- 2.54.0 From 22a22ceaeb3f693207fc4821b55959ba0732d7ed Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 27/94] Prelink tasks for MingGW implibs and MappedFile fixes - Add MappedFile.realign - Fix the case of MappedFile.addNode adding a node in between nodes that have lower alignment than it (by realigning the following node) - Fixup the capacity reservation in addNode to occur after the resize (which may have consumed that capacity) - Remove incorrect path in `.load_host_libc` that was loading mingw libs, they were already being loaded via their build tasks - Generate mingw implibs as a prelink task, so they can be supplied to the linker before prelink() - Any other libraries discovered during Sema are have their implibs generated after, but the self-hosted linker is not passed these. Lld can still use this path. - mingw implib generation now interacts with the progress system - Supply `__ImageBase` for mingw --- src/Compilation.zig | 51 +++++++++++---- src/libs/mingw.zig | 37 ++++++----- src/link.zig | 15 +---- src/link/Coff.zig | 37 ++++++++--- src/link/MappedFile.zig | 80 ++++++++++++++++++++---- test/standalone/shared_library/build.zig | 2 - 6 files changed, 161 insertions(+), 61 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 0d10f21b03939405aab6f9031100ec1cdfc68f7e..ceaf7ef1c829abc4382a190b26955914b304a362 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4475,18 +4475,10 @@ fn performAllTheWork( comp.link_queue.finishZcuQueue(comp); - // This has to happen after the main semantic analysis loop because it is possible for Sema to + // This has to happen again after the main semantic analysis loop because it is possible for Sema to // call `addLinkLib` and hence add more items to `comp.windows_libs`. - for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |link_lib| { - mingw.buildImportLib(comp, link_lib) catch |err| { - // TODO Surface more error details. - comp.lockAndSetMiscFailure( - .windows_import_lib, - "unable to generate DLL import .lib file for {s}: {t}", - .{ link_lib, err }, - ); - }; - } + for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |lib_name| + comp.buildMingwImportLib(lib_name, false, main_progress_node); comp.windows_libs_num_done = @intCast(comp.windows_libs.count()); // Main thread work is all done, now just wait for all async work. @@ -4692,6 +4684,15 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node }); } + while (comp.windows_libs_num_done < comp.windows_libs.count()) { + prelink_group.async( + io, + buildMingwImportLib, + .{ comp, comp.windows_libs.keys()[comp.windows_libs_num_done], true, main_progress_node }, + ); + comp.windows_libs_num_done += 1; + } + prelink_group.await(io) catch |err| switch (err) { error.Canceled => unreachable, // see swapCancelProtection above }; @@ -5377,6 +5378,34 @@ fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std } } +fn buildMingwImportLib(comp: *Compilation, lib_name: []const u8, is_prelink: bool, prog_node: std.Progress.Node) void { + const crt_file_path = mingw.buildImportLib(comp, lib_name, prog_node) catch |err| switch (err) { + // TODO: This isn't actually true for self-hosted + // In the non-prelink case we will end up putting foo.lib onto the linker line and letting the linker + // use its library paths to look for libraries and report any problems. + error.DefNotFound => return if (is_prelink) { + comp.lockAndSetMiscFailure( + .windows_import_lib, + "definition not found for required mingw DLL import .lib {s}", + .{lib_name}, + ); + }, + // TODO Surface more error details. + else => |e| return comp.lockAndSetMiscFailure( + .windows_import_lib, + "unable to generate mingw DLL import .lib file for {s}: {t}", + .{ lib_name, e }, + ), + }; + + if (is_prelink) + comp.queuePrelinkTasks(&.{.{ .load_archive = crt_file_path }}) catch |err| comp.lockAndSetMiscFailure( + .windows_import_lib, + "unable to queue prelink task for mingw import lib {f}: {t}", + .{ crt_file_path, err }, + ); +} + fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_node: std.Progress.Node) void { if (wasi_libc.buildCrtFile(comp, crt_file, prog_node)) |_| { comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = false; diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 2fad57ca1d9a04aee26b2e70d3988aaad9f9dd32..db04d721b33fdcfe61a98ae9116140c225dbad7c 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -207,9 +207,12 @@ fn addCrtCcArgs( }); } -pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { +pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.Progress.Node) !Cache.Path { dev.check(.build_import_lib); + const sub_node = prog_node.start(lib_name, 0); + defer sub_node.end(); + const gpa = comp.gpa; const io = comp.io; @@ -218,12 +221,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const arena = arena_allocator.allocator(); const def_file_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) { - error.FileNotFound => { - log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name }); - // In this case we will end up putting foo.lib onto the linker line and letting the linker - // use its library paths to look for libraries and report any problems. - return; - }, + error.FileNotFound => return error.DefNotFound, else => |e| return e, }; // Only .def.in files need preprocessing @@ -263,14 +261,16 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); try comp.crt_files.ensureUnusedCapacity(gpa, 1); + + const crt_file_path: Cache.Path = .{ + .root_dir = comp.dirs.global_cache, + .sub_path = sub_path, + }; comp.crt_files.putAssumeCapacityNoClobber(final_lib_basename, .{ - .full_object_path = .{ - .root_dir = comp.dirs.global_cache, - .sub_path = sub_path, - }, + .full_object_path = crt_file_path, .lock = man.toOwnedLock(), }); - return; + return crt_file_path; } const digest = man.final(); @@ -294,6 +294,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { } const members = members: { + const members_node = sub_node.start("Members", 0); + defer members_node.end(); + const input = switch (def_needs_preprocessing) { true => pp: { var aw: Io.Writer.Allocating = .init(gpa); @@ -357,13 +360,15 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); + const crt_file_path: Cache.Path = .{ + .root_dir = comp.dirs.global_cache, + .sub_path = lib_final_path, + }; try comp.crt_files.putNoClobber(gpa, final_lib_basename, .{ - .full_object_path = .{ - .root_dir = comp.dirs.global_cache, - .sub_path = lib_final_path, - }, + .full_object_path = crt_file_path, .lock = man.toOwnedLock(), }); + return crt_file_path; } pub fn libExists( diff --git a/src/link.zig b/src/link.zig index 1b7033049f8cfcf21a95671cbc4f732bf339d09d..f76c571b04e577c0d54565a57bf494036b835f00 100644 --- a/src/link.zig +++ b/src/link.zig @@ -482,7 +482,7 @@ pub const File = struct { rpath_list: []const []const u8, /// Zig compiler development linker flags. - /// Enable dumping of linker's state as JSON. + /// Enable dumping of linker's state. enable_link_snapshots: bool, /// Darwin-specific linker flags: @@ -1527,20 +1527,11 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { } } - if (target.os.tag == .windows) { + if (target.os.tag == .windows and target.abi == .msvc) { const inputs: []const struct { dir: enum { crt, msvc_lib, kernel32_lib }, name: []const u8, - } = if (target.abi.isGnu()) switch (comp.config.link_mode) { - .dynamic => &.{ - .{ .dir = .crt, .name = "dllcrt2.obj" }, - .{ .dir = .crt, .name = "libmingw32.lib" }, - }, - .static => &.{ - .{ .dir = .crt, .name = "crt2.obj" }, - .{ .dir = .crt, .name = "libmingw32.lib" }, - }, - } else switch (comp.config.link_mode) { + } = switch (comp.config.link_mode) { .dynamic => &.{ .{ .dir = .msvc_lib, .name = "msvcrt.lib" }, .{ .dir = .msvc_lib, .name = "vcruntime.lib" }, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 8ffda7c95ad603a3cff51b78b3cdb3040b06c656..3c589fa7e9e8edb23afcce4c7221960c7882dc29 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -2017,6 +2017,16 @@ fn initHeaders( .{ .read = true, .write = !is_image }, ); } + + // Linker-supplied symbols + { + const target = &comp.root_mod.resolved_target.result; + if (is_image and target.isMinGW()) { + const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data }); + const sym = si.get(coff); + sym.ni = Node.known.header; + } + } } pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { @@ -2044,7 +2054,7 @@ pub fn endProgress(coff: *Coff) void { coff.mf.update_prog_node = .none; coff.input_prog_node.end(); coff.input_prog_node = .none; - if (!isImage(coff)) { + if (!coff.isImage()) { coff.member_prog_node.end(); coff.member_prog_node = .none; coff.symbol_prog_node.end(); @@ -3103,7 +3113,7 @@ fn objectSectionMapIndex( const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index); - const sn = if (!object_section_gop.found_existing) sn: { + const sym = if (!object_section_gop.found_existing) sn: { try coff.ensureUnusedStringCapacity(name_slice.len); const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice)); const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff); @@ -3140,14 +3150,24 @@ fn objectSectionMapIndex( assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); coff.nodes.appendAssumeCapacity(.{ .object_section = osmi }); - break :sn sym.section_number; - } else object_section_gop.value_ptr.get(coff).section_number; + break :sn sym; + } else object_section_gop.value_ptr.get(coff); + + const parent_ni = sym.ni.parent(&coff.mf); + const parent_alignment = parent_ni.alignment(&coff.mf); + if (alignment.compare(.gt, parent_alignment)) { + log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment }); + parent_ni.realign(&coff.mf, gpa, alignment, true) catch |err| switch (err) { + error.Unimplemented => unreachable, + else => |e| return e, + }; + } try coff.verifyParentSectionAttributes( .object, - sn.name(coff), + sym.section_number.name(coff), name, - .fromFlags(sn.header(coff).flags), + .fromFlags(sym.section_number.header(coff).flags), effective_attributes, ); @@ -4135,7 +4155,7 @@ fn loadObject( }, .weak_external => unreachable, }); - sym.section_number = symbol.section_number; + sym.section_number = section.si.get(coff).section_number; } log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}) = {d}@{d}", .{ @@ -4988,7 +5008,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { const other_ioi = isi.input(coff); if (loc_sym.gmi == .none) { // TODO: We could report the name here if we interned it in loadObject - err.addNote("referenced internally by input '{f}{f}'", .{ + err.addNote("referenced by input '{f}{f}'", .{ other_ioi.path(coff).fmtEscapeString(), fmtMemberNameString(other_ioi.memberName(coff)), }); @@ -5430,6 +5450,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } + // TODO: Only do this if actually referenced? Might have to do on-demand? { // Resolve unresolved .WEAK_EXTERNAL symbols to their aliases const alias_si = sym.weakAlias(); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index ecb3d892e6dafd0861d288d0fc7a2e46f7ba41ec..9d60bc9fc740f5ce72b9bd545ce9dff4d9946ee8 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -351,15 +351,17 @@ pub const Node = extern struct { } /// Moves and expands a node such that its offset and size are aligned to `new_alignment`. - /// + /// If it is possible to move the node backwards, this will be done instead of moving it forward. + /// If `set_alignment` is set, persists `new_alignment` as the node's alignment for future operations. /// Asserts that `ni` is not `Node.Index.root`. pub fn realign( ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, new_alignment: std.mem.Alignment, + set_alignment: bool, ) Error!void { - mf.realignNode(gpa, ni, new_alignment) catch |err| switch (err) { + mf.realignNode(gpa, ni, new_alignment, true, set_alignment) catch |err| switch (err) { error.OutOfMemory, error.Canceled, => |e| return e, @@ -554,6 +556,24 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { }) Error!Node.Index { if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1); const offset = opts.add_node.alignment.forward(@intCast(opts.offset)); + if (opts.parent != .none) { + const new_end = offset + opts.add_node.size; + switch (opts.next) { + .none => { + _, const parent_size = opts.parent.location(mf).resolve(mf); + if (new_end > parent_size) + try opts.parent.resize(mf, gpa, new_end); + }, + else => |next_ni| { + const next_offset, _ = next_ni.location(mf).resolve(mf); + if (new_end > next_offset) + mf.realignNode(gpa, next_ni, opts.add_node.alignment, false, false) catch |err| switch (err) { + error.Unimplemented => unreachable, + else => |e| return e, + }; + }, + } + } const location_tag: Node.Location.Tag, const location_payload: Node.Location.Payload = location: { if (std.math.cast(u32, offset)) |small_offset| break :location .{ .small, .{ .small = .{ .offset = small_offset, .size = 0 }, @@ -595,16 +615,12 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { }, .location_payload = location_payload, }; + { - defer { - free_node.flags.moved = false; - free_node.flags.resized = false; - } - _, const parent_size = opts.parent.location(mf).resolve(mf); - const required_parent_size = offset + opts.add_node.size; - if (required_parent_size > parent_size) - try opts.parent.resize(mf, gpa, required_parent_size); try free_ni.resize(mf, gpa, opts.add_node.size); + if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1); + free_node.flags.moved = false; + free_node.flags.resized = false; } if (opts.add_node.moved) free_ni.movedAssumeCapacity(mf); if (opts.add_node.resized) free_ni.resizedAssumeCapacity(mf); @@ -703,6 +719,7 @@ fn shrinkNode( // This would require unmapping first if (ni == Node.Index.root) return error.Unimplemented; + defer if (std.debug.runtime_safety) mf.verify(); if (node.last != .none) { const last = node.last.get(mf); @@ -725,9 +742,10 @@ fn shrinkNode( const old_file_offset = node.next.fileLocation(mf, false).offset; const new_file_offset = (old_file_offset - old_next_offset) + new_next_offset; @memmove( - mf.memory_map.memory[new_file_offset..][0..next_size], - mf.memory_map.memory[old_file_offset..][0..next_size], + mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(next_size)], + mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(next_size)], ); + @memset(mf.memory_map.memory[@intCast(new_file_offset + next_size)..@intCast(old_file_offset + next_size)], 0); } node.next.setLocationAssumeCapacity(mf, new_next_offset, next_size); @@ -999,6 +1017,8 @@ fn realignNode( gpa: std.mem.Allocator, ni: Node.Index, new_alignment: std.mem.Alignment, + try_backward: bool, + set_alignment: bool, ) (Allocator.Error || Io.Cancelable || IoError)!void { assert(ni != Node.Index.root); // currently unsupported @@ -1009,7 +1029,12 @@ fn realignNode( defer if (std.debug.runtime_safety) mf.verify(); + const prev_alignment = node.flags.alignment; node.flags.alignment = new_alignment; + defer { + // alignment needs to be temporarily set for the resizes below + if (!set_alignment) node.flags.alignment = prev_alignment; + } const new_size = node.flags.alignment.forward(@intCast(size)); if (new_alignment.check(@intCast(old_offset))) { @@ -1026,6 +1051,37 @@ fn realignNode( }, }; + if (try_backward) { + const backward_offset = new_alignment.backward(old_offset); + const prev_end = if (node.prev == .none) 0 else prev: { + const prev_offset, const prev_size = node.prev.location(mf).resolve(mf); + break :prev prev_offset + prev_size; + }; + + if (backward_offset >= prev_end) { + try mf.ensureCapacityForSetLocation(gpa); + + if (node.flags.has_content) { + const old_file_offset = ni.fileLocation(mf, false).offset; + const new_file_offset = (old_file_offset - old_offset) + backward_offset; + @memmove( + mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)], + mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)], + ); + @memset(mf.memory_map.memory[@intCast(new_file_offset + size)..@intCast(old_file_offset + size)], 0); + } + + if (backward_offset + new_size <= trailing_end) { + ni.setLocationAssumeCapacity(mf, backward_offset, new_size); + } else { + ni.setLocationAssumeCapacity(mf, backward_offset, size); + try mf.resizeNode(gpa, ni, new_size); + } + + return; + } + } + const forward_offset = new_alignment.forward(@intCast(old_offset)); if (forward_offset + new_size <= trailing_end) { // Shift into the free space if possible diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 32a9083757dca583f834b2dbd1f26e776629d8d0..2fa0bfdfd694332fce62543a8de8fe30b93ef311 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -20,8 +20,6 @@ pub fn build(b: *std.Build) void { if (!use_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO if (!use_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO if (!use_llvm and target.result.cpu.arch == .s390x) continue; // TODO - if (!use_llvm and target.result.os.tag == .windows and target.result.abi == .gnu and dyn_libc) - continue; // TODO: sub-compilation of compiler_rt failed (failed to link with LLD: LibCInstallationNotAvailable) const lib = b.addLibrary(.{ .linkage = .dynamic, -- 2.54.0 From f5a2bfd95ee75a5ddd57071567052e3482683925 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 28/94] Coff: supply builtins that mingw libc expects - Fix incorrect .alias_si init - Fix not logging addInputSymbol for weak externals - Add builtin init for `__ImageBase` and `__(C|D)TOR_LIST_` when linking mingw libc - Fix up incorrectly concurrently calling `buildMingwImportLib` - Fix `reportUndef` not logging the last undef if there was more than one --- src/Compilation.zig | 19 +++--- src/libs/mingw.zig | 2 + src/link/Coff.zig | 150 +++++++++++++++++++++++++++++++------------- 3 files changed, 119 insertions(+), 52 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index ceaf7ef1c829abc4382a190b26955914b304a362..b5c74b8ad8abdba4412fbcae4092e9e794b0e536 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4475,14 +4475,16 @@ fn performAllTheWork( comp.link_queue.finishZcuQueue(comp); + // Main thread work is all done, now just wait for all async work. + try misc_group.await(io); + // This has to happen again after the main semantic analysis loop because it is possible for Sema to // call `addLinkLib` and hence add more items to `comp.windows_libs`. for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |lib_name| - comp.buildMingwImportLib(lib_name, false, main_progress_node); + misc_group.async(io, buildMingwImportLib, .{ comp, lib_name, false, main_progress_node }); comp.windows_libs_num_done = @intCast(comp.windows_libs.count()); - - // Main thread work is all done, now just wait for all async work. try misc_group.await(io); + comp.link_queue.wait(io); } @@ -4685,11 +4687,12 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node } while (comp.windows_libs_num_done < comp.windows_libs.count()) { - prelink_group.async( - io, - buildMingwImportLib, - .{ comp, comp.windows_libs.keys()[comp.windows_libs_num_done], true, main_progress_node }, - ); + prelink_group.async(io, buildMingwImportLib, .{ + comp, + comp.windows_libs.keys()[comp.windows_libs_num_done], + true, + main_progress_node, + }); comp.windows_libs_num_done += 1; } diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index db04d721b33fdcfe61a98ae9116140c225dbad7c..a19a83e044ce28800257497b1e76bb9fc6cbf9cb 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -210,6 +210,8 @@ fn addCrtCcArgs( pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.Progress.Node) !Cache.Path { dev.check(.build_import_lib); + log.debug("buildImportLib({s})", .{lib_name}); + const sub_node = prog_node.start(lib_name, 0); defer sub_node.end(); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 3c589fa7e9e8edb23afcce4c7221960c7882dc29..babd27f2fec1dedcf019a6e3aff7e9ed5ea74e22 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -753,6 +753,10 @@ pub const String = enum(u32) { @".text" = 20, @".tls$" = 26, @".edata" = 32, + @".ctors" = 39, + @".ctors$ZZZ" = 46, + @".dtors" = 57, + @".dtors$ZZZ" = 64, _, pub const Optional = enum(u32) { @@ -761,6 +765,8 @@ pub const String = enum(u32) { @".text" = @intFromEnum(String.@".text"), @".tls$" = @intFromEnum(String.@".tls$"), @".edata" = @intFromEnum(String.@".edata"), + @".dtors" = @intFromEnum(String.@".dtors"), + @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"), none = std.math.maxInt(u32), _, @@ -1506,6 +1512,7 @@ fn create( section_align, std.fs.path.basename(path.sub_path), ); + try coff.initBuiltins(); return coff; } @@ -2017,15 +2024,58 @@ fn initHeaders( .{ .read = true, .write = !is_image }, ); } +} - // Linker-supplied symbols - { - const target = &comp.root_mod.resolved_target.result; - if (is_image and target.isMinGW()) { +pub fn initBuiltins(coff: *Coff) !void { + const comp = coff.base.comp; + const gpa = comp.gpa; + const target = &comp.root_mod.resolved_target.result; + if (coff.isImage() and target.isMinGW() and comp.config.link_libc) { + try coff.symbols.ensureUnusedCapacity(gpa, 5); + try coff.globals.ensureUnusedCapacity(gpa, 2); + try coff.nodes.ensureUnusedCapacity(gpa, 3); + + { const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data }); const sym = si.get(coff); sym.ni = Node.known.header; } + + const lists: []const struct { global: []const u8, start: String, end: String } = &.{ + .{ .global = "__CTOR_LIST__", .start = .@".ctors", .end = .@".ctors$ZZZ" }, + .{ .global = "__DTOR_LIST__", .start = .@".dtors", .end = .@".dtors$ZZZ" }, + }; + + 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_sym = start_osmi.symbol(coff).get(coff); + try start_sym.ni.resize(&coff.mf, gpa, addr_info.size); + const start_slice = start_sym.ni.slice(&coff.mf); + switch (addr_info.magic) { + _ => unreachable, + inline .PE32, .@"PE32+" => |t| { + const addr: *TargetAddr(t) = @ptrCast(@alignCast(start_slice)); + // For __CTOR_LIST__ -1 indicates that the list is null terminated. + // For __DTOR_LIST__, this value is ignored. + coff.targetStore(addr, std.math.maxInt(TargetAddr(t))); + }, + } + + // Any .(c|d)tor$(.*) input sections will merge in between these sections + // TODO: is it guaranteed that there will be no padding between those nodes? + + const end_sym = end_osmi.symbol(coff).get(coff); + try end_sym.ni.resize(&coff.mf, gpa, addr_info.size); + @memset(end_sym.ni.slice(&coff.mf), 0); + + const list_si = try coff.globalSymbol(.{ .name = list.global, .type = .data }); + const list_sym = list_si.get(coff); + list_sym.ni = start_sym.ni; + list_sym.section_number = start_sym.section_number; + } } } @@ -2146,6 +2196,28 @@ fn computeSymbolSectionOffset(coff: *Coff, sym: *const Symbol) u32 { pub inline fn targetEndian(_: *const Coff) std.lang.Endian { return .little; } + +fn targetAddrInfo(coff: *Coff) struct { + size: u64, + alignment: std.mem.Alignment, + magic: std.coff.OptionalHeader.Magic, +} { + const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); + switch (magic) { + _ => unreachable, + .PE32 => return .{ .size = 4, .alignment = .@"4", .magic = magic }, + .@"PE32+" => return .{ .size = 8, .alignment = .@"8", .magic = magic }, + } +} + +fn TargetAddr(comptime magic: std.coff.OptionalHeader.Magic) type { + return switch (magic) { + _ => comptime unreachable, + .PE32 => u32, + .@"PE32+" => u64, + }; +} + fn targetLoad(coff: *const Coff, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child { const Child = @typeInfo(@TypeOf(ptr)).pointer.child; return switch (@typeInfo(Child)) { @@ -4072,7 +4144,19 @@ fn loadObject( coff.synth_prog_node.increaseEstimatedTotalItems(1); } - for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, psi| { + for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, i| { + defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}, {d}) = {d}@{d}", .{ + symbol.name.toSlice(coff), + index, + symbol.value, + symbol.section_number, + switch (symbol.value) { + inline else => |v| v, + }, + symbol.si, + symbol.si.get(coff).section_number, + }); + const section = switch (symbol.section_number) { .UNDEFINED => switch (symbol.value) { .section, @@ -4101,7 +4185,7 @@ fn loadObject( ); if (alias.si == .null) { - alias.weak_external_psi = .wrap(@intCast(psi)); + alias.weak_external_psi = .wrap(@intCast(i)); } else { sym.setValue(.{ .alias_si = alias.si }); } @@ -4137,9 +4221,9 @@ fn loadObject( } } - if (symbol.weak_external_psi.unwrap()) |i| { + if (symbol.weak_external_psi.unwrap()) |weak_external_i| { assert(symbol.si != .null); - pending_symbols.values()[i].si.get(coff).value = .{ .alias_si = symbol.si }; + pending_symbols.values()[weak_external_i].si.get(coff).setValue(.{ .alias_si = symbol.si }); } if (section.si != symbol.si) { @@ -4157,17 +4241,6 @@ fn loadObject( }); sym.section_number = section.si.get(coff).section_number; } - - log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}) = {d}@{d}", .{ - symbol.name.toSlice(coff), - index, - symbol.value, - switch (symbol.value) { - inline else => |v| v, - }, - symbol.si, - section.si.get(coff).section_number, - }); } const relocation_size = std.coff.Relocation.sizeOf(); @@ -4979,11 +5052,11 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { var start_i: usize = 0; var num_unique_references: usize = 1; - for (undef_indices.items[0..], 0..) |reloc_i, i| { + for (0..undef_indices.items.len) |i| { const target = coff.relocs.items[undef_indices.items[start_i]].target; - if (target != coff.relocs.items[reloc_i].target or i == undef_indices.items.len - 1) { + if (i == undef_indices.items.len - 1 or target != coff.relocs.items[undef_indices.items[i + 1]].target) { defer { - start_i = i; + start_i = i + 1; num_unique_references = 1; } @@ -4995,7 +5068,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.globalName(coff).name.toSlice(coff)}); var prev_loc_si: Symbol.Index = .null; - for (undef_indices.items[start_i..][0..@max(1, i - start_i)]) |reference_i| { + 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; @@ -5007,7 +5080,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { .input_section => |isi| { const other_ioi = isi.input(coff); if (loc_sym.gmi == .none) { - // TODO: We could report the name here if we interned it in loadObject + // TODO: We could report non-global names here if we intern them in loadObject err.addNote("referenced by input '{f}{f}'", .{ other_ioi.path(coff).fmtEscapeString(), fmtMemberNameString(other_ioi.memberName(coff)), @@ -5022,8 +5095,6 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { }, .import_thunk => |gmi| err.addNote("referenced by import thunk for '{s}'", .{ gmi.globalName(coff).name.toSlice(coff), - // TODO: This won't always have a ZCU - //comp.zcu.?.root_mod.fully_qualified_name, }), inline .nav, .uav, @@ -5583,12 +5654,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { try coff.symbols.ensureUnusedCapacity(gpa, 1); const target_endian = coff.targetEndian(); - const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); - const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { - _ => unreachable, - .PE32 => .{ 4, .@"4" }, - .@"PE32+" => .{ 8, .@"8" }, - }; + const addr_info = coff.targetAddrInfo(); const gop = try coff.import_table.entries.getOrPutAdapted( gpa, lib_name, @@ -5606,13 +5672,13 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { import_hint_name_align.forward(lib_name.len + ".dll".len + 1); const idata_section_ni = coff.import_table.ni.parent(&coff.mf); const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ - .size = addr_size * 2, - .alignment = addr_align, + .size = addr_info.size * 2, + .alignment = addr_info.alignment, .moved = true, }); const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ - .size = addr_size * 2, - .alignment = addr_align, + .size = addr_info.size * 2, + .alignment = addr_info.alignment, .moved = true, }); const import_address_table_si = coff.addSymbolAssumeCapacity(); @@ -5677,7 +5743,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { iat_symbol_gop.value_ptr.* = import_symbol_index; gop.value_ptr.len = import_symbol_index + 1; - const new_symbol_table_size = addr_size * (import_symbol_index + 2); + const new_symbol_table_size = addr_info.size * (import_symbol_index + 2); const opt_name = import.name.toSlice(coff); const opt_import_hint_name_index = if (opt_name) |name| blk: { @@ -5704,7 +5770,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf); const import_address_slice = import_address_table_ni.slice(&coff.mf); - switch (magic) { + switch (addr_info.magic) { _ => unreachable, inline .PE32, .@"PE32+" => |ct_magic| { const Payload = packed union(u31) { @@ -5749,7 +5815,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { } assert(sym.loc_relocs == .none); - const iat_offset: u32 = @intCast(addr_size * iat_symbol_gop.value_ptr.*); + const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*); switch (import.kind) { .iat_ptr => { const iat_sym = gop.value_ptr.import_address_table_si.get(coff); @@ -5960,11 +6026,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { switch (magic) { _ => unreachable, inline .PE32, .@"PE32+" => |ct_magic| { - const Addr = switch (ct_magic) { - _ => comptime unreachable, - .PE32 => u32, - .@"PE32+" => u64, - }; + 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( -- 2.54.0 From 8bf109b0b34d1d97a71a447ab4d79a89125a2de2 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 29/94] Coff: weak external symbol resolutions, more .drectve argument support - Track weak external type for resolution later - Wait until exports are known before resolving weak externals / alternate names - Support /DEFAULTLIB, /INCLUDE, /ALTERNATENAME in .drectve - Resolve /DEFAULTLIB during prelink - Start on support linking images with no zcu --- src/link/Coff.zig | 527 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 408 insertions(+), 119 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index babd27f2fec1dedcf019a6e3aff7e9ed5ea74e22..89c142f28417129c057782040cfa5108e2e19291 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -19,6 +19,7 @@ const Value = @import("../Value.zig"); const Zcu = @import("../Zcu.zig"); const ModuleDefinition = @import("../libs/mingw/def.zig").ModuleDefinition; const implib = @import("../libs/mingw/implib.zig"); +const Path = std.Build.Cache.Path; base: link.File, mf: MappedFile, @@ -35,16 +36,19 @@ inputs: std.ArrayHashMapUnmanaged(std.Build.Cache.Path, void, std.Build.Cache.Pa input_archives: std.ArrayList(InputArchive), input_archive_members: std.ArrayList(InputArchive.Member), input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol), -input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct { - first: InputArchive.Member.Symbol.Index, - last: InputArchive.Member.Symbol.Index, -}), +input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, InputArchive.SearchList), pending_input: ?InputArchive.Member.Index, +pending_default_libs: std.ArrayList(struct { + path: []const u8, + ioi: InputObject.Index, +}), +alternate_names: std.AutoArrayHashMapUnmanaged(String, String), input_objects: std.ArrayList(InputObject), input_symbols: std.ArrayList(Symbol.Index), input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, inputs_complete: bool, +exports_complete: bool, strings: std.HashMapUnmanaged( u32, void, @@ -58,6 +62,8 @@ object_section_table: std.array_hash_map.Auto(String, Symbol.Index), symbols: std.ArrayList(Symbol), globals: std.array_hash_map.Auto(GlobalName, Symbol.Index), global_pending_index: u32, +late_globals: std.ArrayList(Node.GlobalMapIndex), +late_globals_pending_index: u32, navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index), uavs: std.array_hash_map.Auto(InternPool.Index, Symbol.Index), lazy: std.EnumArray(link.File.LazySymbol.Kind, struct { @@ -73,6 +79,7 @@ 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; @@ -435,6 +442,11 @@ pub const InputArchive = struct { }; }; }; + + pub const SearchList = struct { + first: InputArchive.Member.Symbol.Index, + last: InputArchive.Member.Symbol.Index, + }; }; pub const InputObject = struct { @@ -825,6 +837,23 @@ pub const Section = struct { pub const GlobalName = struct { name: String, lib_name: String.Optional }; +pub const WeakExternalStrat = enum(u2) { + no_library, + library, + alias, + anti_dependency, + + pub fn fromFlag(flag: std.coff.WeakExternalFlag) WeakExternalStrat { + return switch (flag) { + .SEARCH_NOLIBRARY => .no_library, + .SEARCH_LIBRARY => .library, + .SEARCH_ALIAS => .alias, + .ANTI_DEPENDENCY => .anti_dependency, + _ => unreachable, + }; + } +}; + pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, @@ -833,7 +862,9 @@ pub const Symbol = struct { value_tag: ValueTag, type: Symbol.Type, dll_storage_class: DllStorageClass, - _: u10 = 0, + // Only defined for .alias_si and .alias_name + weak_external_strat: WeakExternalStrat, + _: u8 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -859,15 +890,22 @@ pub const Symbol = struct { const ValueTag = enum(u2) { node_offset, alias_si, + alias_name, size, }; pub const Value = union(ValueTag) { - /// The offset of the symbol within it's node + /// The offset of the symbol within its node. Used with symbols that + /// don't create their own nodes: .input_section, .import_address_table node_offset: u32, - /// For undefined globals, this is a weak alias - /// that can replace this symbol, or .null if none exists + /// This is a weak alias that can replace this symbol + /// Globals only. alias_si: Symbol.Index, + /// For weak externals that have an alias that is also an undef + /// external, this is the name of the alias global that should + /// be generated if this symbol is not resolved. + /// Globals only. + alias_name: String, /// The symbol size, or 0 if unknown size: u32, }; @@ -897,10 +935,6 @@ pub const Symbol = struct { }; } - pub fn weakAlias(sym: *const Symbol) Symbol.Index { - return if (sym.flags.value_tag == .alias_si) sym.value.alias_si else .null; - } - pub fn size(sym: *const Symbol) u32 { return if (sym.flags.value_tag == .size) sym.value.size else 0; } @@ -1465,11 +1499,14 @@ fn create( .input_archive_symbols = .empty, .input_archive_symbol_indices = .empty, .pending_input = null, + .pending_default_libs = .empty, + .alternate_names = .empty, .input_objects = .empty, .input_symbols = .empty, .input_sections = .empty, .input_section_pending_index = 0, .inputs_complete = false, + .exports_complete = false, .strings = .empty, .string_bytes = .empty, .section_table = .empty, @@ -1478,6 +1515,8 @@ fn create( .symbols = .empty, .globals = .empty, .global_pending_index = 0, + .late_globals = .empty, + .late_globals_pending_index = 0, .navs = .empty, .uavs = .empty, .lazy = .initFill(.{ @@ -1491,6 +1530,7 @@ fn create( .symbol_prog_node = .none, .member_prog_node = .none, .input_prog_node = .none, + .subsystem = options.subsystem, .dump_snapshot = options.enable_link_snapshots, }; errdefer coff.deinit(); @@ -1533,6 +1573,9 @@ pub fn deinit(coff: *Coff) void { coff.input_archive_members.deinit(gpa); coff.input_archive_symbols.deinit(gpa); coff.input_archive_symbol_indices.deinit(gpa); + for (coff.pending_default_libs.items) |l| gpa.free(l.path); + coff.pending_default_libs.deinit(gpa); + coff.alternate_names.deinit(gpa); coff.input_objects.deinit(gpa); coff.input_symbols.deinit(gpa); coff.input_sections.deinit(gpa); @@ -1543,6 +1586,7 @@ pub fn deinit(coff: *Coff) void { coff.object_section_table.deinit(gpa); coff.symbols.deinit(gpa); coff.globals.deinit(gpa); + coff.late_globals.deinit(gpa); coff.navs.deinit(gpa); coff.uavs.deinit(gpa); for (&coff.lazy.values) |*lazy| lazy.map.deinit(gpa); @@ -1579,8 +1623,12 @@ fn isObj(coff: *const Coff) bool { return coff.base.comp.config.output_mode == .Obj; } -fn zcuSectionParent(coff: *Coff) MappedFile.Node.Index { - assert(coff.base.comp.zcu != null); +fn hasCoffHeader(coff: *const Coff) bool { + return coff.base.comp.zcu != null or !coff.isArchive(); +} + +fn sectionParent(coff: *Coff) MappedFile.Node.Index { + assert(coff.hasCoffHeader()); return if (coff.isArchive()) Node.known.zcu_member else Node.known.file; } @@ -1611,7 +1659,7 @@ fn initHeaders( 0; var expected_nodes_len: usize = Node.known_count; - if (comp.zcu != null) { + if (coff.hasCoffHeader()) { // Sections expected_nodes_len += 3; @@ -1663,7 +1711,7 @@ fn initHeaders( @memcpy(signature_slice, archive_signature); } - const opt_zcu_coff_parent_ni = if (is_archive) parent: { + const opt_coff_parent_ni = if (is_archive) parent: { const initial_member_count = Member.Index.known_count + @intFromBool(comp.zcu != null); try coff.members.ensureTotalCapacity(gpa, initial_member_count); @@ -1709,10 +1757,10 @@ fn initHeaders( if (placeholder_ni == Node.known.zcu_member) break; } - break :parent if (comp.zcu != null) Node.known.header else null; + break :parent Node.known.header; }; - const zcu_coff_parent_ni = opt_zcu_coff_parent_ni orelse { + const coff_parent_ni = opt_coff_parent_ni orelse { // If we're not generating any code, no more known nodes are used while (coff.nodes.len < Node.known_count) { _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{}); @@ -1723,7 +1771,7 @@ fn initHeaders( }; const coff_header_ni = Node.known.coff_header; - assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{ .size = @sizeOf(std.coff.Header), .alignment = .@"4", .fixed = true, @@ -1751,7 +1799,7 @@ fn initHeaders( } const optional_header_ni = Node.known.optional_header; - assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{ .size = optional_header_size, .alignment = .@"4", .fixed = true, @@ -1863,7 +1911,7 @@ fn initHeaders( } const data_directories_ni = Node.known.data_directories; - assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{ .size = data_directories_size, .alignment = .@"4", .fixed = true, @@ -1879,7 +1927,7 @@ fn initHeaders( } const section_table_ni = Node.known.section_table; - assert(section_table_ni == try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + assert(section_table_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{ .alignment = .@"4", .fixed = true, })); @@ -1889,14 +1937,14 @@ fn initHeaders( if (!is_image) { // TODO: These two nodes could be inside one movable node? - coff.symbol_table.ni = try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + coff.symbol_table.ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{ .alignment = .@"2", .fixed = true, .moved = true, }); coff.nodes.appendAssumeCapacity(.symbol_table); - coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, zcu_coff_parent_ni, .{ + coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{ .size = @sizeOf(u32), .fixed = true, .resized = true, @@ -2030,16 +2078,19 @@ pub fn initBuiltins(coff: *Coff) !void { const comp = coff.base.comp; 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; + } + if (coff.isImage() and target.isMinGW() and comp.config.link_libc) { - try coff.symbols.ensureUnusedCapacity(gpa, 5); + try coff.symbols.ensureUnusedCapacity(gpa, 6); try coff.globals.ensureUnusedCapacity(gpa, 2); - try coff.nodes.ensureUnusedCapacity(gpa, 3); - - { - const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data }); - const sym = si.get(coff); - sym.ni = Node.known.header; - } + try coff.nodes.ensureUnusedCapacity(gpa, 6); const lists: []const struct { global: []const u8, start: String, end: String } = &.{ .{ .global = "__CTOR_LIST__", .start = .@".ctors", .end = .@".ctors$ZZZ" }, @@ -2083,7 +2134,10 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { prog_node.increaseEstimatedTotalItems(3); coff.const_prog_node = prog_node.start("Constants", coff.pending_uavs.count()); coff.synth_prog_node = prog_node.start("Synthetics", count: { - var count = coff.globals.count() - coff.global_pending_index; + var count = + coff.globals.count() - coff.global_pending_index + + coff.late_globals.items.len - coff.late_globals_pending_index; + for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index; break :count count; }); @@ -2246,7 +2300,7 @@ fn targetStore(coff: *const Coff, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).poi } pub fn headerPtr(coff: *Coff) *std.coff.Header { - assert(coff.base.comp.zcu != null); + assert(coff.hasCoffHeader()); return @ptrCast(@alignCast(Node.known.coff_header.slice(&coff.mf))); } @@ -2405,6 +2459,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .value_tag = .size, .type = .unknown, .dll_storage_class = .default, + .weak_external_strat = undefined, }, .loc_relocs = .none, .target_relocs = .none, @@ -2509,7 +2564,6 @@ fn getOrPutGlobalSymbol( if (!sym_gop.found_existing) { const si = coff.addSymbolAssumeCapacity(); const sym = si.get(coff); - sym.setValue(.{ .alias_si = .null }); sym.gmi = .wrap(@intCast(sym_gop.index)); sym.flags.type = opts.type; sym.flags.dll_storage_class = opts.dll_storage_class; @@ -2982,7 +3036,7 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { } fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { - assert(coff.base.comp.zcu != null); + assert(coff.hasCoffHeader()); const gpa = coff.base.comp.gpa; try coff.nodes.ensureUnusedCapacity(gpa, 1); @@ -3000,7 +3054,7 @@ fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !S @sizeOf(std.coff.SectionHeader) * section_table_len, ); - const ni = try coff.mf.addLastChildNode(gpa, coff.zcuSectionParent(), .{ + const ni = try coff.mf.addLastChildNode(gpa, coff.sectionParent(), .{ .alignment = coff.mf.flags.block_size, .moved = true, .bubbles_moved = false, @@ -3185,7 +3239,7 @@ fn objectSectionMapIndex( const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index); - const sym = if (!object_section_gop.found_existing) sn: { + const sym = if (!object_section_gop.found_existing) sym: { try coff.ensureUnusedStringCapacity(name_slice.len); const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice)); const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff); @@ -3222,7 +3276,7 @@ fn objectSectionMapIndex( assert(sym.loc_relocs == .none); sym.loc_relocs = @enumFromInt(coff.relocs.items.len); coff.nodes.appendAssumeCapacity(.{ .object_section = osmi }); - break :sn sym; + break :sym sym; } else object_section_gop.value_ptr.get(coff); const parent_ni = sym.ni.parent(&coff.mf); @@ -3334,7 +3388,7 @@ pub fn addReloc( const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); if (section.relocation_table_ni == .none) { try coff.nodes.ensureUnusedCapacity(gpa, 1); - section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, coff.zcuSectionParent(), .{ + section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, coff.sectionParent(), .{ .size = new_size, .alignment = .@"2", .moved = true, @@ -3637,13 +3691,6 @@ fn loadObject( section_i, section_name_slice, }); - - if (section.header.flags.LNK_REMOVE or - section.header.flags.MEM_DISCARDABLE) - { - // TODO: Convert .debug$* sections into PDB - continue; - } } break :sections sections; @@ -3684,14 +3731,16 @@ fn loadObject( section: u32, // Offset within the section static: u32, - // If section is defined, the symbol size. Otherwise offset within the section. + // If section is undefined, the symbol size. Otherwise offset within the section. external: u32, - // The index of the target symbol of this alias + // The index of the target symbol of this weak external weak_external: u32, + // Trails .weak_external + weak_external_aux: WeakExternalStrat, }, section_number: Symbol.SectionNumber, si: Symbol.Index, - // The index of the weak_external that targest this symbol + // If a weak external targets this symbol, the index of the weak external weak_external_psi: PendingSymbolIndex, }; @@ -3741,11 +3790,12 @@ fn loadObject( const psi: PendingSymbolIndex = .wrap(@intCast(pending_symbols.count())); const section_number: Symbol.SectionNumber = @enumFromInt(@intFromEnum(symbol.section_number)); - const opt_value: ?@FieldType(PendingSymbol, "value") = pending_symbol: switch (symbol.storage_class) { + + const values: []const @FieldType(PendingSymbol, "value") = pending_symbols: switch (symbol.storage_class) { .STATIC, .LABEL => |storage_class| switch (section_number) { // TODO: Do we need to do anything with @feat.00? // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392 - .UNDEFINED, .DEBUG, .ABSOLUTE => null, + .UNDEFINED, .DEBUG, .ABSOLUTE => &.{}, else => |sn| { const section = §ions[sn.toIndex()]; @@ -3803,10 +3853,10 @@ fn loadObject( section.psi = psi; } - break :pending_symbol if (is_section) + break :pending_symbols &.{if (is_section) .{ .section = section.header.size_of_raw_data } else - .{ .static = symbol.value }; + .{ .static = symbol.value }}; }, }, .WEAK_EXTERNAL => switch (symbol.section_number) { @@ -3830,16 +3880,12 @@ fn loadObject( .{ weak_external.tag_index, symbol_i }, ); - break :pending_symbol switch (weak_external.flag) { - .SEARCH_NOLIBRARY, - .SEARCH_LIBRARY, - => return diags.failParse( - path, - "TODO handle weak external characteristic 0x{x} for symbol 0x{x}", - .{ weak_external.flag, symbol_i }, - ), - .SEARCH_ALIAS => .{ .weak_external = weak_external.tag_index }, - else => return diags.failParse( + break :pending_symbols switch (weak_external.flag) { + else => |flag| &.{ + .{ .weak_external = weak_external.tag_index }, + .{ .weak_external_aux = WeakExternalStrat.fromFlag(flag) }, + }, + _ => return diags.failParse( path, "encountered unknown weak external characteristic 0x{x} for symbol 0x{x}", .{ weak_external.flag, symbol_i }, @@ -3853,7 +3899,7 @@ fn loadObject( ), }, .EXTERNAL => switch (section_number) { - .UNDEFINED => .{ .external = symbol.value }, + .UNDEFINED => &.{.{ .external = symbol.value }}, .ABSOLUTE => return diags.failParse( path, "TODO unhandled external absolute symbol 0x{x}: '{s}'", @@ -3864,7 +3910,7 @@ fn loadObject( "unexpected external symbol 0x{x} in DEBUG section: '{s}'", .{ symbol_i, name }, ), - else => .{ .external = symbol.value }, + else => &.{.{ .external = symbol.value }}, }, .FILE => { if (!std.mem.eql(u8, name, ".file")) @@ -3878,7 +3924,7 @@ fn loadObject( @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]); input.source_name = (try coff.getOrPutString(file.getFileName())).toOptional(); - break :pending_symbol null; + break :pending_symbols &.{}; }, else => |storage_class| return diags.failParse( path, @@ -3887,10 +3933,13 @@ fn loadObject( ), }; - if (opt_value) |value| { + for (values, 0..) |value, i| { switch (value) { .section => {}, - .static, .external, .weak_external => { + .static, + .external, + .weak_external, + => { num_global_symbols += 1; if (section_number.hasIndex()) { const section = §ions[section_number.toIndex()]; @@ -3899,10 +3948,11 @@ fn loadObject( section.comdat_psi = psi; } }, + .weak_external_aux => {}, } const symbol_name = coff.getOrPutStringAssumeCapacity(name); - pending_symbols.putAssumeCapacity(symbol_i, .{ + pending_symbols.putAssumeCapacity(symbol_i + @as(u32, @intCast(i)), .{ .name = symbol_name, .value = value, .section_number = section_number, @@ -3917,6 +3967,7 @@ fn loadObject( if (section.header.flags.LNK_INFO) { if (std.mem.eql(u8, §ion.header.name, ".drectve")) { try fr.seekTo(fl.offset + section.header.pointer_to_raw_data); + // TODO: Don't really want an additional buffer here, but want to limit to size_of_raw_data var buf: [128]u8 = undefined; var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf); while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) { @@ -3926,9 +3977,60 @@ fn loadObject( // Microsoft tools emit 3 space characters into this section even with /Zl if (arg.len == 0) continue; - if (std.mem.cutPrefix(u8, arg, "-exclude-symbols:")) |rest| { - // TODO: When implementing mingw auto-exports, use this to not export this symbol - _ = rest; + if (std.ascii.startsWithIgnoreCase(arg, "-exclude-symbols:")) { + // TODO: When implementing mingw auto-exports (if at all?), use this to not export this symbol + } else if (std.ascii.startsWithIgnoreCase(arg, "/include:")) { + _ = try coff.globalSymbol(.{ .name = arg["/include:".len..] }); + } else if (std.ascii.startsWithIgnoreCase(arg, "/alternatename:")) { + var split = std.mem.splitScalar(u8, arg["/alternatename:".len..], '='); + const orig = split.first(); + const alt = split.next() orelse + return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg}); + + try coff.ensureManyUnusedStringCapacity(2, orig.len + alt.len + 2); + const orig_str = coff.getOrPutStringAssumeCapacity(orig); + const alt_str = coff.getOrPutStringAssumeCapacity(alt); + const gop = try coff.alternate_names.getOrPut(gpa, orig_str); + if (!gop.found_existing) { + log.debug("alternateName({s}={s})", .{ orig, alt }); + gop.value_ptr.* = alt_str; + } else if (gop.value_ptr.* != alt_str) + return diags.failParse( + path, + "conflicting /alternatename .drectve arguments: first seen as {s}={s}, now seen as {s}={s}", + .{ orig, gop.value_ptr.toSlice(coff), orig, alt }, + ); + } else if (std.ascii.startsWithIgnoreCase(arg, "/guardsym:")) { + // TODO: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata + } else if (std.ascii.startsWithIgnoreCase(arg, "/merge:")) { + var split = std.mem.splitScalar(u8, arg["/merge:".len..], '='); + const from = split.first(); + const to = split.next() orelse + return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg}); + + // TODO: Override the parent selection for generated sections below + _ = from; + _ = to; + } else if (std.ascii.startsWithIgnoreCase(arg, "/disallowlib:")) { + const lib_name = arg["/disallowlib:".len..]; + // TODO: Track these and issue error in prelink if any match + _ = lib_name; + } else if (std.ascii.startsWithIgnoreCase(arg, "/defaultlib:")) { + const lib_path = arg["/defaultlib:".len..]; + const trim = std.mem.trim(u8, lib_path, "\""); + if (lib_path.len == trim.len or lib_path.len - 2 == trim.len) { + if (!comp.config.link_libc or comp.libc_installation == null) + return diags.failParse(path, "encountered /DEFAULTLIB .drectve argument when libc was not available: {s}", .{arg}); + + (try coff.pending_default_libs.addOne(gpa)).* = .{ + .path = try gpa.dupe(u8, lib_path), + .ioi = ioi, + }; + } else return diags.failParse( + path, + "malformed /DEFAULTLIB .drectve argument: `{s}`", + .{arg}, + ); } else return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg}); } } @@ -3940,6 +4042,7 @@ fn loadObject( if (section.header.flags.LNK_REMOVE or section.header.flags.MEM_DISCARDABLE) { + // TODO: Convert .debug$* sections into PDB section.comdat_result = .skip; continue; } @@ -3973,6 +4076,7 @@ fn loadObject( const symbol = &pending_symbols.values()[psi]; const si = existing: switch (symbol.value) { .weak_external => unreachable, + .weak_external_aux => unreachable, .static => break :comdat .include, .section => { assert(section.comdat_psi == .none); @@ -4145,14 +4249,21 @@ fn loadObject( } for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, i| { - defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}, {d}) = {d}@{d}", .{ + switch (symbol.value) { + .weak_external_aux => continue, + else => {}, + } + + defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}, {d}) = n{d} {d}@{d}", .{ symbol.name.toSlice(coff), index, symbol.value, - symbol.section_number, switch (symbol.value) { + .weak_external_aux => unreachable, inline else => |v| v, }, + symbol.section_number, + symbol.si.get(coff).ni, symbol.si, symbol.si.get(coff).section_number, }); @@ -4161,34 +4272,50 @@ fn loadObject( .UNDEFINED => switch (symbol.value) { .section, .static, + .weak_external_aux, => unreachable, - .external, - .weak_external, - => |value, tag| { + .external => { + if (symbol.weak_external_psi.unwrap()) |weak_external_i| { + // If the alias itself is an undef external, we need to wait until flushing the weak + // external global before creating a global for the alias, as another input + // could still provide the weak external. + const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff); + weak_sym.setValue(.{ .alias_name = symbol.name }); + weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux; + } + + // Deferred until referenced by a reloc in this object. + // vcruntime.lib defines symbols like this (ie. memcpy_$fo$) that are not referenced + continue; + }, + .weak_external => |alias_index| { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); symbol.si = global_gop.value_ptr.*; if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { const sym = symbol.si.get(coff); - if (tag == .external) { - sym.setValue(.{ .size = @max(sym.size(), value) }); - } else { - const alias = pending_symbols.getPtr(value) orelse - return diags.failParse( - path, - "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}", - .{ - index, - symbol.name.toSlice(coff), - fmtMemberNameString(member_name), - value, - }, - ); + const alias = pending_symbols.getPtr(alias_index) orelse + return diags.failParse( + path, + "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}", + .{ + index, + symbol.name.toSlice(coff), + fmtMemberNameString(member_name), + alias_index, + }, + ); - if (alias.si == .null) { - alias.weak_external_psi = .wrap(@intCast(i)); - } else { - sym.setValue(.{ .alias_si = alias.si }); - } + if (alias.si == .null and alias_index > index) { + // 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, + } else .{ + .alias_si = alias.si, + }); + sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux; } } @@ -4217,13 +4344,17 @@ fn loadObject( if (global_gop.found_existing and sym.ni != .none) return coff.failMultipleDefinitions(path, member_name, symbol.name, index, global_gop.value_ptr.*, .none); }, - .weak_external => unreachable, + .weak_external, + .weak_external_aux, + => unreachable, } } if (symbol.weak_external_psi.unwrap()) |weak_external_i| { assert(symbol.si != .null); - pending_symbols.values()[weak_external_i].si.get(coff).setValue(.{ .alias_si = symbol.si }); + const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff); + weak_sym.setValue(.{ .alias_si = symbol.si }); + weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux; } if (section.si != symbol.si) { @@ -4237,7 +4368,9 @@ fn loadObject( .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable, else => .{ .node_offset = v }, }, - .weak_external => unreachable, + .weak_external, + .weak_external_aux, + => unreachable, }); sym.section_number = section.si.get(coff).section_number; } @@ -4260,14 +4393,34 @@ fn loadObject( if (target_endian != native_endian) std.mem.byteSwapAllFields(std.coff.Relocation, &reloc); - // TODO: This error should show member name for lib - const symbol = pending_symbols.get(reloc.symbol_table_index) orelse + const symbol = pending_symbols.getPtr(reloc.symbol_table_index) orelse return diags.failParse( path, - "relocation 0x{x} in section '{s}'{f} targets invalid symbol index 0x{x}", - .{ reloc_i, section.name.toSlice(coff), fmtMemberNameString(member_name), reloc.symbol_table_index }, + "relocation 0x{x} in section '{s}' of {f}{f} targets invalid symbol index 0x{x}", + .{ + reloc_i, + section.name.toSlice(coff), + path.fmtEscapeString(), + fmtMemberNameString(member_name), + reloc.symbol_table_index, + }, ); + if (symbol.si == .null) { + assert(symbol.section_number == .UNDEFINED); + switch (symbol.value) { + .external => |size| { + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + symbol.si = global_gop.value_ptr.*; + if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { + const sym = symbol.si.get(coff); + sym.setValue(.{ .size = @max(sym.size(), size) }); + } + }, + else => unreachable, + } + } + assert(symbol.si != .null); try coff.addReloc( section.si, @@ -4299,7 +4452,7 @@ fn loadObject( var prev_sn: Symbol.SectionNumber = .DEBUG; var include_section = false; for (pending_symbols.values()) |symbol| { - // The symbol may have not been included, or it's an undefined external + // The symbol may have not been included, or it's an undefined external / aux if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue; if (prev_sn != symbol.section_number) { @@ -4731,6 +4884,71 @@ pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { _ = prog_node; 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; + + defer { + for (coff.pending_default_libs.items) |l| gpa.free(l.path); + coff.pending_default_libs.clearAndFree(gpa); + } + + assert(comp.config.link_libc); + const libc_installation = comp.libc_installation.?; + const all_paths: [3]?[]const u8 = .{ + libc_installation.crt_dir, + libc_installation.msvc_lib_dir, + libc_installation.kernel32_lib_dir, + }; + const search_paths = all_paths[0..if (target.abi == .msvc or target.abi == .itanium) 3 else 1]; + lib: for (coff.pending_default_libs.items) |lib| { + if (!std.mem.eql(u8, std.fs.path.extension(lib.path), ".lib")) + return comp.link_diags.failParse( + lib.ioi.path(coff), + "/DEFAULTLIB library '{s}' had unexpected extension", + .{lib.path}, + ); + + log.debug("loadDefaultLib({s}, {f})", .{ lib.path, lib.ioi.path(coff) }); + for (search_paths) |opt_path| if (opt_path) |search_path| { + const lib_path = try Path.initCwd(search_path).join(arena, lib.path); + const archive = link.openObject(comp.io, lib_path, false, false) catch |err| switch (err) { + error.FileNotFound => { + arena.free(lib_path.sub_path); + continue; + }, + else => |e| return comp.link_diags.failParse( + lib.ioi.path(coff), + "error opening /DEFAULTLIB library '{s}': {t}", + .{ lib.path, e }, + ), + }; + errdefer archive.file.close(comp.io); + + coff.loadInput(.{ .archive = archive }) catch |err| switch (err) { + error.LinkFailure => return, + else => |e| return comp.link_diags.failParse( + lib.ioi.path(coff), + "error loading /DEFAULTLIB library '{s}': {t}", + .{ lib.path, e }, + ), + }; + + break :lib; + }; + + return comp.link_diags.failParse( + lib.ioi.path(coff), + "/DEFAULTLIB library '{s}' was not found", + .{lib.path}, + ); + } + } + coff.inputs_complete = true; } @@ -5143,6 +5361,11 @@ pub fn flush( ) !void { _ = arena; _ = prog_node; + + // TODO: When https://github.com/ziglang/zig/issues/23617 is in, + // this should be set after updateExports instead + coff.exports_complete = true; + while (try coff.idle(tid)) {} if (coff.isImage()) @@ -5221,6 +5444,22 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }) coff.global_pending_index += 1; break :task; } + if (coff.exports_complete and coff.late_globals_pending_index < coff.late_globals.items.len) { + const gmi: Node.GlobalMapIndex = coff.late_globals.items[coff.late_globals_pending_index]; + const sub_prog_node = coff.synth_prog_node.start( + gmi.globalName(coff).name.toSlice(coff), + 0, + ); + defer sub_prog_node.end(); + if (coff.flushGlobal(gmi) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => |e| return comp.link_diags.fail( + "linker failed to lower constant: {t}", + .{e}, + ), + }) coff.late_globals_pending_index += 1; + 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 }; @@ -5357,6 +5596,7 @@ 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; + if (coff.exports_complete and coff.late_globals.items.len > coff.late_globals_pending_index) 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; @@ -5504,10 +5744,11 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const gn = gmi.globalName(coff); const si = gmi.symbol(coff); const sym = si.get(coff); + const is_late = gmi.unwrap().? < coff.global_pending_index; log.debug( - "flushGlobal({s}, {?s}) = {d} ({d})", - .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), si, sym.ni }, + "flushGlobal({s}, {?s}, {}) = {d} ({d})", + .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), is_late, si, sym.ni }, ); if (!coff.isImage()) { @@ -5521,16 +5762,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } - // TODO: Only do this if actually referenced? Might have to do on-demand? - { - // Resolve unresolved .WEAK_EXTERNAL symbols to their aliases - const alias_si = sym.weakAlias(); - if (alias_si != .null) { - try coff.aliasGlobal(gmi, alias_si); - return true; - } - } - const Import = struct { lib_name: String, name: String.Optional, @@ -5555,9 +5786,41 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { break :name .{ coff.getOrPutStringAssumeCapacity(name), true }; }; - // TODO: Try to search for __imp_ even when !is_imp, we want to not use thunks if we can + const opt_alt_search_name = coff.alternate_names.get(search_name); + const search_libs = if (is_late) switch (sym.flags.value_tag) { + .alias_si, .alias_name => switch (sym.flags.weak_external_strat) { + .no_library => false, + .library, + .alias, + => true, + .anti_dependency => return comp.link_diags.fail( + // TODO: Figure out what the purpose of this is + "TODO support anti_dependency weak external: {s}", + .{gn.name.toSlice(coff)}, + ), + }, + else => true, + } else search_libs: { + if (switch (sym.flags.value_tag) { + .alias_si, .alias_name => true, + else => opt_alt_search_name != null, + }) { + // We need to wait until all exports are known before resolving these + coff.synth_prog_node.increaseEstimatedTotalItems(1); + (try coff.late_globals.addOne(gpa)).* = gmi; + return true; + } - if (coff.input_archive_symbol_indices.get(search_name)) |indices_list| { + break :search_libs true; + }; + + const opt_indices_lists: []const ?InputArchive.SearchList = if (search_libs) &.{ + coff.input_archive_symbol_indices.get(search_name), + if (opt_alt_search_name) |alt| coff.input_archive_symbol_indices.get(alt) else null, + } else &.{}; + + for (opt_indices_lists) |opt_indices_list| { + const indices_list = opt_indices_list orelse continue; var iter: InputArchive.Member.Symbol.Index = indices_list.first; while (true) { const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)]; @@ -5631,6 +5894,32 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { } } + switch (sym.flags.value_tag) { + .alias_si => { + assert(is_late); + try coff.aliasGlobal(gmi, sym.value.alias_si); + return true; + }, + .alias_name => { + assert(is_late); + // Convert an unresolved weak external that itself refers to an undef external + // into a (possibly new) global, so it can be resolved separately. + const alias_gop = try coff.getOrPutGlobalSymbol(.{ .name = sym.value.alias_name.toSlice(coff) }); + try coff.aliasGlobal(gmi, alias_gop.value_ptr.*); + return true; + }, + else => {}, + } + + // If there was an object that had the alternate name, we've attempted to load it + if (opt_alt_search_name) |alt_search_name| { + assert(is_late); + if (coff.globals.get(.{ .name = alt_search_name, .lib_name = .none })) |alias_si| { + try coff.aliasGlobal(gmi, alias_si); + return true; + } + } + // Allow importing symbols with no implib entry, if a lib_name was specified. // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification, // which are not in the implib. -- 2.54.0 From a2f4459da80937e8ca2a19d6298c90690f7ddfcd Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 30/94] 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 --- src/link/Coff.zig | 230 ++++++++++++++++++++++++++++------------ src/link/MappedFile.zig | 6 +- 2 files changed, 167 insertions(+), 69 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 89c142f28417129c057782040cfa5108e2e19291..186bf91761f2bd783645aec71d1eba6d4037e210 100644 --- 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 index 9d60bc9fc740f5ce72b9bd545ce9dff4d9946ee8..a910d25869175c456083ff926c5beac3a5a483d6 100644 --- 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; -- 2.54.0 From 9325187fd212d73d39f6189241e12084a58e571b Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 31/94] Coff: fix aliasGlobal - Fixup aliasGlobal not properly linking up the relocs lists --- src/link/Coff.zig | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 186bf91761f2bd783645aec71d1eba6d4037e210..d3d649d3ca560a336d446ae473013f27d368fe63 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1032,7 +1032,7 @@ pub const Symbol = struct { const sym = si.get(coff); sym.rva = coff.computeNodeRva(sym.ni) + sym.nodeOffset(coff); si.applyLocationRelocs(coff); - si.applyTargetRelocs(coff); + si.applyTargetRelocs(coff, .none); } pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { @@ -1065,7 +1065,7 @@ pub const Symbol = struct { } } - pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff) void { + 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 @@ -1075,7 +1075,7 @@ pub const Symbol = struct { } var ri = sym.target_relocs; - while (ri != .none) { + while (ri != end) { const reloc = ri.get(coff); assert(reloc.target == si); reloc.apply(coff); @@ -3403,7 +3403,7 @@ pub fn addReloc( const target = target_si.get(coff); const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len); - log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d}{s}) = {d}", .{ + log.debug("addReloc({d}@{d}+0x{x} -> {d}@{d}+0x{x}{s}) = {d}", .{ loc_si, loc_si.get(coff).section_number, offset, @@ -4306,15 +4306,15 @@ fn loadObject( else => {}, } - defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}, {d}) = n{d} {d}@{d}", .{ + defer log.debug("addInputSymbol({s}, 0x{x}@{d}, {t}=0x{x}) = n{d} {d}@{d}", .{ symbol.name.toSlice(coff), index, + symbol.section_number, symbol.value, switch (symbol.value) { .weak_external_aux => unreachable, inline else => |v| v, }, - symbol.section_number, symbol.si.get(coff).ni, symbol.si, symbol.si.get(coff).section_number, @@ -5799,10 +5799,18 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v const gn = gmi.globalName(coff); const si = gmi.symbol(coff); const sym = si.get(coff); + const alias_sym = alias_si.get(coff); assert(sym.section_number == .UNDEFINED); assert(sym.loc_relocs == .none); - const alias_sym = alias_si.get(coff); + log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{ + gn.name.toSlice(coff), + gn.lib_name.toSlice(coff), + si, + alias_si, + if (alias_sym.gmi != .none) alias_sym.gmi.globalName(coff).name.toSlice(coff) else null, + }); + var ri = sym.target_relocs; while (ri != .none) { const reloc = ri.get(coff); @@ -5812,22 +5820,19 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v reloc.next = alias_sym.target_relocs; if (alias_sym.target_relocs != .none) alias_sym.target_relocs.get(coff).prev = ri; + break; } ri = reloc.next; } + const prev_target_relocs = alias_sym.target_relocs; + if (sym.target_relocs != .none) + alias_sym.target_relocs = sym.target_relocs; sym.target_relocs = .none; sym.gmi = alias_sym.gmi; coff.globals.values()[gmi.unwrap().?] = alias_si; - alias_si.applyTargetRelocs(coff); - - log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{ - gn.name.toSlice(coff), - gn.lib_name.toSlice(coff), - si, - alias_si, - if (alias_sym.gmi != .none) alias_sym.gmi.globalName(coff).name.toSlice(coff) else null, - }); + // Only apply the new relocs + alias_si.applyTargetRelocs(coff, prev_target_relocs); } fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { @@ -5839,8 +5844,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const is_late = gmi.unwrap().? < coff.global_pending_index; log.debug( - "flushGlobal({s}, {?s}, {}) = {d} ({d})", - .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), is_late, si, sym.ni }, + "flushGlobal({s}, {?s}, {}) = n{d} {d}@{d}", + .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), is_late, sym.ni, si, sym.section_number }, ); if (!coff.isImage()) { @@ -6759,7 +6764,7 @@ fn updateExportsInner( export_sym.rva = exported_sym.rva; export_sym.setValue(.{ .size = exported_sym.value.size }); export_sym.section_number = exported_sym.section_number; - defer export_si.applyTargetRelocs(coff); + defer export_si.applyTargetRelocs(coff, .none); if (coff.isImage()) { if (@"export".opts.name.eqlSlice("_tls_used", ip)) { -- 2.54.0 From def87a6efb03197d2387dbbe91f02d1676427034 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 32/94] Coff: add .bss and differentiate between initialized / uninitialized sections when merging - Add .bss section - Set up initialized / uninitialized flags for existing sections --- src/link/Coff.zig | 67 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index d3d649d3ca560a336d446ae473013f27d368fe63..cf9713a63865e7feac226c586088b3200de67384 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -796,16 +796,21 @@ pub const String = enum(u32) { @".ctors$ZZZ" = 46, @".dtors" = 57, @".dtors$ZZZ" = 64, + @".bss" = 75, _, pub const Optional = enum(u32) { @".data" = @intFromEnum(String.@".data"), + @".idata" = @intFromEnum(String.@".idata"), @".rdata" = @intFromEnum(String.@".rdata"), @".text" = @intFromEnum(String.@".text"), @".tls$" = @intFromEnum(String.@".tls$"), @".edata" = @intFromEnum(String.@".edata"), + @".ctors" = @intFromEnum(String.@".ctors"), + @".ctors$ZZZ" = @intFromEnum(String.@".ctors$ZZZ"), @".dtors" = @intFromEnum(String.@".dtors"), @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"), + @".bss" = @intFromEnum(String.@".bss"), none = std.math.maxInt(u32), _, @@ -1000,6 +1005,7 @@ pub const Symbol = struct { pub const Index = enum(u32) { null, + bss, data, rdata, text, @@ -1707,7 +1713,7 @@ fn initHeaders( var expected_nodes_len: usize = Node.known_count; if (coff.hasCoffHeader()) { // Sections - expected_nodes_len += 3; + expected_nodes_len += 4; if (is_image) // Pseudo-sections and import / export table @@ -2000,6 +2006,14 @@ fn initHeaders( try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); assert(coff.addSymbolAssumeCapacity() == .null); + // TODO: How do we tell MappedFile not to allocate physical space for these? + // TODO: Could have a node flag 'virtual' that can never have slice* called on it or fileLocation + + assert(try coff.addSection(.@".bss", .{ + .CNT_UNINITIALIZED_DATA = true, + .MEM_READ = true, + .MEM_WRITE = true, + }) == .bss); assert(try coff.addSection(.@".data", .{ .CNT_INITIALIZED_DATA = true, .MEM_READ = true, @@ -2021,7 +2035,7 @@ fn initHeaders( (try coff.objectSectionMapIndex( .@".idata", coff.mf.flags.block_size, - .{ .read = true }, + .{ .read = true, .initialized = true }, )).symbol(coff).node(coff), .{ .alignment = .@"4", .moved = true }, ); @@ -2030,7 +2044,7 @@ fn initHeaders( coff.export_table.ni = (try coff.pseudoSectionMapIndex( .@".edata", .of(std.coff.ExportDirectoryTable), - .{ .read = true }, + .{ .read = true, .initialized = true }, )).symbol(coff).node(coff); coff.export_table.export_directory_table_ni = try coff.mf.addLastChildNode( @@ -2115,7 +2129,7 @@ fn initHeaders( _ = try coff.objectSectionMapIndex( .@".tls$", coff.mf.flags.block_size, - .{ .read = true, .write = !is_image }, + .{ .read = true, .write = !is_image, .initialized = true }, ); } } @@ -2145,12 +2159,12 @@ pub fn initBuiltins(coff: *Coff) !void { const start_osmi = try coff.objectSectionMapIndex( list.start, addr_info.alignment, - .{ .read = true }, + .{ .read = true, .initialized = true }, ); const end_osmi = try coff.objectSectionMapIndex( list.end, addr_info.alignment, - .{ .read = true }, + .{ .read = true, .initialized = true }, ); const start_sym = start_osmi.symbol(coff).get(coff); @@ -2657,13 +2671,13 @@ fn navSection( const ip = &zcu.intern_pool; const default: String, const attributes: ObjectSectionAttributes = if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{ - .@".tls$", .{ .read = true, .write = true }, + .@".tls$", .{ .read = true, .write = true, .initialized = true }, } else if (ip.isFunctionType(nav_resolved.type)) .{ .@".text", .{ .read = true, .execute = true }, } else if (nav_resolved.@"const") .{ - .@".rdata", .{ .read = true }, + .@".rdata", .{ .read = true, .initialized = true }, } else .{ - .@".data", .{ .read = true, .write = true }, + .@".data", .{ .read = true, .write = true, .initialized = true }, }; return (try coff.objectSectionMapIndex( @@ -3180,6 +3194,8 @@ const ObjectSectionAttributes = packed struct { nocache: bool = false, discard: bool = false, remove: bool = false, + initialized: bool = false, + uninitialized: bool = false, // TODO: Include init / not init flags? @@ -3193,6 +3209,8 @@ const ObjectSectionAttributes = packed struct { .nocache = flags.MEM_NOT_CACHED, .discard = flags.MEM_DISCARDABLE, .remove = flags.LNK_REMOVE, + .initialized = flags.CNT_INITIALIZED_DATA, + .uninitialized = flags.CNT_UNINITIALIZED_DATA, }; } @@ -3206,6 +3224,8 @@ const ObjectSectionAttributes = packed struct { .MEM_NOT_CACHED = attr.nocache, .MEM_DISCARDABLE = attr.discard, .LNK_REMOVE = attr.remove, + .CNT_INITIALIZED_DATA = attr.uninitialized, + .CNT_UNINITIALIZED_DATA = attr.uninitialized, }; } }; @@ -3220,7 +3240,9 @@ fn pseudoSectionMapIndex( const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name); const psmi: Node.PseudoSectionMapIndex = @enumFromInt(pseudo_section_gop.index); const sn = if (!pseudo_section_gop.found_existing) sn: { - const default_parent: Symbol.Index = if (attributes.execute) + const default_parent: Symbol.Index = if (attributes.uninitialized) + .bss + else if (attributes.execute) .text else if (attributes.write) .data @@ -3401,6 +3423,8 @@ 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}", .{ @@ -4194,6 +4218,7 @@ fn loadObject( const existing_crc = switch (coff.getNode(sym.ni)) { .input_section => |isi| isi.inputSection(coff).crc, // TODO: Should this result be cached somewhere? + // TODO: Is this slice triggering has_content = true un-necessarily? Check section for init data flag. else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)), }; @@ -5750,7 +5775,7 @@ fn flushUav( const sec_si = (try coff.objectSectionMapIndex( .@".rdata", coff.mf.flags.block_size, - .{ .read = true }, + .{ .read = true, .initialized = true }, )).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); @@ -6335,15 +6360,19 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .archive_member, => {}, .image_section => |si| { - const file_offset = if (isArchive(coff)) - si.get(coff).ni.location(&coff.mf).resolve(&coff.mf)[0] - else - ni.fileLocation(&coff.mf, false).offset; + const sym = si.get(coff); + const flags = coff.targetLoad(&sym.section_number.header(coff).flags); + if (!flags.CNT_UNINITIALIZED_DATA) { + const file_offset = if (isArchive(coff)) + sym.ni.location(&coff.mf).resolve(&coff.mf)[0] + else + ni.fileLocation(&coff.mf, false).offset; - return coff.targetStore( - &si.get(coff).section_number.header(coff).pointer_to_raw_data, - @intCast(file_offset), - ); + return coff.targetStore( + &sym.section_number.header(coff).pointer_to_raw_data, + @intCast(file_offset), + ); + } }, .input_section => |isi| { isi.symbol(coff).flushMoved(coff); -- 2.54.0 From a98a4f465797a7205f9b1cefedfcb4957b65d593 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 33/94] 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 --- src/crash_report.zig | 9 +- src/link.zig | 10 +- src/link/Coff.zig | 247 +++++++++++++++++++++++++++---------------- src/link/Elf2.zig | 10 +- 4 files changed, 180 insertions(+), 96 deletions(-) diff --git a/src/crash_report.zig b/src/crash_report.zig index 6bdfd3f4751d0ddd0f28acbbbca1f6e1c9faf592..1b99d98f922eb6feabdd356dc9a09b9b214cc328 100644 --- 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 index f76c571b04e577c0d54565a57bf494036b835f00..8d3203df40936d99bf179097e92fdf497484573d 100644 --- 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 index cf9713a63865e7feac226c586088b3200de67384..080907d2e8d7ebf2a777b4722c999ffd1c9c166a 100644 --- 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 index d0ee6e49bed4fbf5997f73970e0a28d4034395bb..992f36d3f616b4f4a049da28689b00ad5f13e82b 100644 --- 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( -- 2.54.0 From 587030e75440d26a52cc65e58d14a66a4f459096 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 34/94] - Fixup linker snapshot crash report output --- src/crash_report.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/crash_report.zig b/src/crash_report.zig index 1b99d98f922eb6feabdd356dc9a09b9b214cc328..4294527c4511fa756af417a0c4b12e24701d421d 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -131,16 +131,17 @@ fn dumpCrashContext() Io.Writer.Error!void { } else if (AnalyzeBody.current) |anal| { try dumpCrashContextSema(anal, w, &S.crash_heap); } else if (LinkerOp.current) |linker_op| { - try w.writeAll("Linker snapshot:\n\n"); + try w.writeAll("Linker snapshot:\n"); if (build_options.enable_link_snapshots) { switch (try linker_op.lf.dump(w, linker_op.tid)) { - .unsupported => try w.writeAll("(backend does not support link snapshots))"), + .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"), + .enabled => {}, } } else { try w.writeAll("(build with -Dlink-snapshot to dump linker state)"); } + try w.writeAll("\n\n"); } else { try w.writeAll("(no context)\n\n"); } -- 2.54.0 From ae1130ab2090ce25cd709c89749da2e3cd161d2f Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 35/94] Coff: stub out merging and handle special case sections - Parse / flush /MERGE arguments, impl is incomplete - Fixup recovering addends not sign extending - Add .fptable section when linking msvc libc - this needs to be a separate section as it gets marked read-only at runtime - Pseudo sections prefer to use the exact section name if it exists already (to support .fptable) --- src/link/Coff.zig | 248 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 193 insertions(+), 55 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 080907d2e8d7ebf2a777b4722c999ffd1c9c166a..7e7460207124e04993f78b2bec5dd9b496cf5633 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -60,6 +60,8 @@ string_bytes: std.ArrayList(u8), section_table: std.AutoArrayHashMapUnmanaged(String, Section), pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), +section_merges: std.AutoArrayHashMapUnmanaged(String, String), +section_merge_pending_index: u32, symbols: std.ArrayList(Symbol), globals: std.array_hash_map.Auto(GlobalName, Symbol.Index), global_pending_index: u32, @@ -93,6 +95,8 @@ pub const archive_end_of_header = "`\n"; pub const imp_prefix = "__imp_"; +const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len; + /// This is the start of a Portable Executable (PE) file. /// It starts with a MS-DOS header followed by a MS-DOS stub program. /// This data does not change so we include it as follows in all binaries. @@ -797,6 +801,7 @@ pub const String = enum(u32) { @".dtors" = 57, @".dtors$ZZZ" = 64, @".bss" = 75, + @".fptable" = 80, _, pub const Optional = enum(u32) { @@ -811,6 +816,7 @@ pub const String = enum(u32) { @".dtors" = @intFromEnum(String.@".dtors"), @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"), @".bss" = @intFromEnum(String.@".bss"), + @".fptable" = @intFromEnum(String.@".fptable"), none = std.math.maxInt(u32), _, @@ -1242,11 +1248,6 @@ pub const Reloc = extern struct { .ADDR32, .ADDR32NB, .SECREL, - => std.mem.readInt( - u32, - loc_slice[0..4], - target_endian, - ), .REL32, .REL32_1, .REL32_2, @@ -1263,11 +1264,6 @@ pub const Reloc = extern struct { else => |kind| @panic(@tagName(kind)), .ABSOLUTE => 0, .DIR16, - => std.mem.readInt( - u16, - loc_slice[0..2], - target_endian, - ), .REL16, => std.mem.readInt( i16, @@ -1277,11 +1273,6 @@ pub const Reloc = extern struct { .DIR32, .DIR32NB, .SECREL, - => std.mem.readInt( - u32, - loc_slice[0..4], - target_endian, - ), .REL32, => std.mem.readInt( i32, @@ -1553,6 +1544,8 @@ fn create( .section_table = .empty, .pseudo_section_table = .empty, .object_section_table = .empty, + .section_merges = .empty, + .section_merge_pending_index = 0, .symbols = .empty, .globals = .empty, .global_pending_index = 0, @@ -1698,7 +1691,7 @@ fn initHeaders( const file_align: std.mem.Alignment = comptime .fromByteUnits(default_file_alignment); const is_image = coff.isImage(); const is_archive = coff.isArchive(); - + const target = &comp.root_mod.resolved_target.result; const optional_header_size: u16 = if (is_image) switch (magic) { _ => unreachable, inline else => |ct_magic| @sizeOf(@field(std.coff.OptionalHeader, @tagName(ct_magic))), @@ -1713,12 +1706,14 @@ fn initHeaders( // Sections expected_nodes_len += 4; - if (is_image) + if (is_image) { // Pseudo-sections and import / export table - expected_nodes_len += 9 - else - // Symbol table - expected_nodes_len += 2; + expected_nodes_len += 9; + if (comp.config.link_libc and target.abi == .msvc) + expected_nodes_len += 1; + } else + // Symbol table + expected_nodes_len += 2; // TLS section if (comp.config.any_non_single_threaded) { @@ -2004,9 +1999,10 @@ fn initHeaders( try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); assert(coff.addSymbolAssumeCapacity() == .null); + // TODO: How do we tell MappedFile not to allocate physical space for these? // TODO: Could have a node flag 'virtual' that can never have slice* called on it or fileLocation - + // TODO: Instead of it's own section, we can place .bss as a pseudo-section at the end of .text in the extra space assert(try coff.addSection(.@".bss", .{ .CNT_UNINITIALIZED_DATA = true, .MEM_READ = true, @@ -2028,6 +2024,18 @@ fn initHeaders( }) == .text); if (is_image) { + if (comp.config.link_libc and target.abi == .msvc) { + // This section contains a function pointer table used by control flow guard: + // https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard + // The page containing it is set to PAGE_READONLY during startup, so this can't + // be merged into .data this protection would overlap writable memory. + _ = try coff.addSection(.@".fptable", .{ + .CNT_INITIALIZED_DATA = true, + .MEM_READ = true, + .MEM_WRITE = true, + }); + } + coff.import_table.ni = try coff.mf.addLastChildNode( gpa, (try coff.objectSectionMapIndex( @@ -2199,7 +2207,8 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { coff.synth_prog_node = prog_node.start("Synthetics", count: { var count = coff.globals.count() - coff.global_pending_index + - coff.late_globals.items.len - coff.late_globals_pending_index; + coff.late_globals.items.len - coff.late_globals_pending_index + + coff.section_merges.count() - coff.section_merge_pending_index; for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index; break :count count; @@ -2562,7 +2571,8 @@ fn getString(coff: *Coff, string: []const u8) String.Optional { fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !SymbolTable.SymbolName { assert(!coff.isImage()); const gpa = coff.base.comp.gpa; - return if (name.len > 8) name: { + + return if (name.len > header_name_max_len) name: { const string = opt_string orelse try coff.getOrPutString(name); const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string); if (!string_gop.found_existing) { @@ -3202,8 +3212,6 @@ const ObjectSectionAttributes = packed struct { initialized: bool = false, uninitialized: bool = false, - // TODO: Include init / not init flags? - pub fn fromFlags(flags: std.coff.SectionHeader.Flags) ObjectSectionAttributes { return .{ .read = flags.MEM_READ, @@ -3244,26 +3252,22 @@ fn pseudoSectionMapIndex( const gpa = coff.base.comp.gpa; const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name); const psmi: Node.PseudoSectionMapIndex = @enumFromInt(pseudo_section_gop.index); - const sn = if (!pseudo_section_gop.found_existing) sn: { - const default_parent: Symbol.Index = if (attributes.uninitialized) - .bss - else if (attributes.execute) - .text - else if (attributes.write) - .data - else - .rdata; + const parent_sn = if (!pseudo_section_gop.found_existing) sn: { + const effective_name = coff.section_merges.get(name) orelse name; + const parent = if (coff.section_table.get(effective_name)) |existing_sec| + existing_sec.si + else if (coff.isImage()) parent: { + const parent: Symbol.Index = if (attributes.uninitialized) + .bss + else if (attributes.execute) + .text + else if (attributes.write) + .data + else + .rdata; - const parent = if (coff.isImage() or std.mem.eql( - u8, - name.toSlice(coff), - default_parent.knownString().toSlice(coff).?, - )) - default_parent - else if (coff.section_table.get(name)) |section| - section.si - else - try coff.addSection(name, attributes.asFlags()); + break :parent parent; + } else try coff.addSection(effective_name, attributes.asFlags()); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -3282,9 +3286,9 @@ fn pseudoSectionMapIndex( try coff.verifyParentSectionAttributes( .pseudo, - sn.name(coff), + parent_sn.name(coff), name, - .fromFlags(sn.header(coff).flags), + .fromFlags(parent_sn.header(coff).flags), attributes, ); @@ -3614,6 +3618,8 @@ fn loadObject( const target_endian = coff.targetEndian(); const is_archive = coff.isArchive(); assert(!coff.isObj()); + // We want to evaluate new merges as we see them in .drectve sections to avoid redundant work + assert(coff.section_merge_pending_index == coff.section_merges.count()); log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) }); @@ -3826,10 +3832,15 @@ fn loadObject( var num_global_symbols: u32 = 0; var pending_symbols: std.AutoArrayHashMapUnmanaged(u32, PendingSymbol) = .empty; defer pending_symbols.deinit(gpa); - if (!is_archive) try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); + var section_merges: std.ArrayList(struct { + from: String, + to: String, + }) = .empty; + defer section_merges.deinit(gpa); + // Discover symbol names and COMDAT symbol mappings var symbol_i: u32 = 0; while (symbol_i < header.number_of_symbols) { @@ -4081,15 +4092,48 @@ fn loadObject( ); } else if (std.ascii.startsWithIgnoreCase(arg, "/guardsym:")) { // TODO: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata - } else if (std.ascii.startsWithIgnoreCase(arg, "/merge:")) { + } else if (std.ascii.startsWithIgnoreCase(arg, "/merge:")) merge: { var split = std.mem.splitScalar(u8, arg["/merge:".len..], '='); const from = split.first(); const to = split.next() orelse return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg}); + if (to.len > header_name_max_len) + return diags.failParse( + path, + "/merge .drectve target exceeds max length of {d}: '{s}'", + .{ header_name_max_len, arg }, + ); + if (std.mem.eql(u8, from, to)) break :merge; - // TODO: Override the parent selection for generated sections below - _ = from; - _ = to; + try coff.ensureManyUnusedStringCapacity(2, from.len + to.len + 2); + const from_str = coff.getOrPutStringAssumeCapacity(from); + const to_str = coff.getOrPutStringAssumeCapacity(to); + + { + var iter = to_str; + while (coff.section_merges.get(iter)) |next_to| { + if (next_to == from_str) + return diags.failParse( + path, + "/merge .drectve argument would create a cycle: {s}={s} leads to {s}={s}", + .{ from, to, iter.toSlice(coff), to }, + ); + + iter = next_to; + } + } + + try coff.section_merges.ensureUnusedCapacity(gpa, 1); + const gop = coff.section_merges.getOrPutAssumeCapacity(from_str); + if (!gop.found_existing) { + coff.synth_prog_node.increaseEstimatedTotalItems(1); + gop.value_ptr.* = to_str; + } else if (gop.value_ptr.* != to_str) + return diags.failParse( + path, + "conflicting /merge .drectve arguments: first seen as {s}={s}, now seen as {s}={s}", + .{ from, gop.value_ptr.toSlice(coff), from, to }, + ); } else if (std.ascii.startsWithIgnoreCase(arg, "/disallowlib:")) { const lib_name = arg["/disallowlib:".len..]; // TODO: Track these and issue error in prelink if any match @@ -4250,6 +4294,9 @@ fn loadObject( }; } + while (coff.section_merge_pending_index < coff.section_merges.count()) : (coff.section_merge_pending_index += 1) + try coff.flushSectionMerge(coff.section_merge_pending_index); + // Resolve pending associations, create parent sections var num_included_sections: u16 = 0; var num_included_symbols: u32 = 0; @@ -4276,6 +4323,11 @@ fn loadObject( .pending => unreachable, } + // TODO: Until we support sorting .pdata, we shouldn't merge these in, the result would be invalid + const section_name = section.name.toSlice(coff); + if (std.mem.startsWith(u8, section_name, ".pdata")) + continue; + num_included_sections += 1; num_included_symbols += section.num_symbols; num_included_relocs += section.header.number_of_relocations; @@ -4293,7 +4345,7 @@ fn loadObject( try coff.input_sections.ensureUnusedCapacity(gpa, num_included_sections); for (sections) |*section| { - if (section.comdat_result != .include) continue; + if (section.parent_si == .null) continue; const ni = try coff.mf.addLastChildNode(gpa, section.parent_si.node(coff), .{ .size = section.header.size_of_raw_data, @@ -4457,7 +4509,7 @@ fn loadObject( const relocation_size = std.coff.Relocation.sizeOf(); for (sections) |section| { - if (section.comdat_result != .include) continue; + if (section.si == .null) continue; const loc_sym = section.si.get(coff); assert(loc_sym.loc_relocs == .none); @@ -5481,6 +5533,26 @@ pub fn flush( pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { const comp = coff.base.comp; task: { + while (coff.section_merge_pending_index < coff.section_merges.count()) { + defer coff.section_merge_pending_index += 1; + const sub_prog_node = coff.synth_prog_node.start( + coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff), + 0, + ); + defer sub_prog_node.end(); + coff.flushSectionMerge(coff.section_merge_pending_index) catch |err| switch (err) { + //error.OutOfMemory => |e| return e, + else => |e| return comp.link_diags.fail( + "linker failed to merge section {s} into {s}: {t}", + .{ + coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff), + coff.section_merges.values()[coff.section_merge_pending_index].toSlice(coff), + e, + }, + ), + }; + break :task; + } while (coff.pending_uavs.pop()) |pending_uav| { const sub_prog_node = coff.idleProgNode(tid, coff.const_prog_node, .{ .uav = pending_uav.key }); defer sub_prog_node.end(); @@ -5655,7 +5727,8 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { try coff.flushMember(pending_mi.key); break :task; } - // TODO: This and the next task ideally only run once, as it's wasteful otherwise + // TODO: All the sort / shrink tasks ideally run only once - otherwise it's wasteful + // Defer until exports_complete? if (coff.export_table.pending_sort) { defer coff.export_table.pending_sort = false; const sub_prog_node = coff.idleProgNode( @@ -5694,6 +5767,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } } + if (coff.section_merge_pending_index < coff.section_merges.count()) return true; 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; @@ -6805,6 +6879,70 @@ fn flushExportsSort(coff: *Coff) void { }); } +fn flushSectionMerge(coff: *Coff, index: u32) !void { + assert(coff.isImage()); + const from = coff.section_merges.keys()[index]; + const to = coff.section_merges.values()[index]; + assert(from != to); + + log.debug("flushSectionMerge({s}->{s})", .{ from.toSlice(coff), to.toSlice(coff) }); + + const opt_to_sec = coff.section_table.getPtr(to); + if (coff.section_table.getPtr(from)) |from_sec| { + const from_sym = from_sec.si.get(coff); + if (opt_to_sec) |to_sec| { + const to_sym = to_sec.si.get(coff); + + // TODO: Create a pseudo-section named `from` in `to`, copy `from_sec` ni into that pseudo section + // TODO: Update .section_number for all contained syms + // TODO: Remove `from_sec` from section table (set size = 0 and can do it in flushResized?). + // This is non-trivial as we can't leave holes in the section table. + // TODO: Merge section flags + _ = to_sym; + + return coff.base.comp.link_diags.fail("TODO implement section to section merge", .{}); + } else if (coff.pseudo_section_table.get(to)) |to_ps_si| { + const to_sym = to_ps_si.get(coff); + if (from_sym.section_number == to_sym.section_number) + return; + + // TODO: Same as above, except place `from` into a node in `to_psmi`'s parent + return coff.base.comp.link_diags.fail("TODO implement section to pseudosection merge", .{}); + } + + // If `to` doesn't exist, /MERGE is defined as renaming `from` to `to`. + // No other path will create image-level sections, so we can safely rename this now + const from_name = &from_sec.si.get(coff).section_number.header(coff).name; + const to_slice = to.toSlice(coff); + @memcpy(from_name[0..to_slice.len], to_slice); + @memset(from_name[to_slice.len..], 0); + } else if (coff.pseudo_section_table.getIndex(from)) |from_index| { + const from_psmi: Node.PseudoSectionMapIndex = @enumFromInt(from_index); + const from_sym = from_psmi.symbol(coff).get(coff); + if (opt_to_sec) |to_sec| { + const to_sym = to_sec.si.get(coff); + if (from_sym.section_number == to_sym.section_number) + return; + + // TODO: Move from_psmi's node into to_sec + // TODO: Update .section_number for all contained syms + // TODO: Merge section flags + + return coff.base.comp.link_diags.fail("TODO implement pseudosection to section merge", .{}); + } else if (coff.pseudo_section_table.get(to)) |to_ps_si| { + const to_sym = to_ps_si.get(coff); + if (from_sym.section_number == to_sym.section_number) + return; + + // TODO: Same as above, but move from_psmi's node after to_psmi's node in its parent + + return coff.base.comp.link_diags.fail("TODO implement pseudosection to pseudosection merge", .{}); + } + + // Renaming pseudo-sections have no effect on the output, so this is a no-op. + } +} + fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void { var rva = start_rva; for ( -- 2.54.0 From 303521cd14e17099be7aade312e83ea539c67b42 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 36/94] MappedFile: fixup resize node shifting when siblings have different alignments --- src/link/MappedFile.zig | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index a910d25869175c456083ff926c5beac3a5a483d6..9363e11a2cfc16c7f09471958ecd226ce77d40c7 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -334,6 +334,7 @@ pub const Node = extern struct { } pub fn resize(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, size: u64) Error!void { + defer if (std.debug.runtime_safety) mf.verify(); mf.resizeNode(gpa, ni, size) catch |err| switch (err) { error.OutOfMemory, error.Canceled, @@ -900,6 +901,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested var last_fixed_ni = ni; var first_floating_ni = node.next; var shift = new_size - old_size; + var max_shift_align: std.mem.Alignment = .@"1"; var direction: enum { forward, reverse } = .forward; while (true) { assert(last_fixed_ni != .none); @@ -916,9 +918,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); + max_shift_align = max_shift_align.max(first_floating.flags.alignment.max(last_fixed.flags.alignment)); if (first_floating.flags.fixed) { - shift = shift_alignment.forward(@intCast( + shift = max_shift_align.forward(@intCast( @max(shift, first_floating_size), )); @@ -930,7 +932,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 = shift_alignment.forward( + const new_first_floating_offset = max_shift_align.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; @@ -991,7 +993,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested last_fixed_ni.setLocationAssumeCapacity( mf, old_last_fixed_offset, - last_fixed_size + shift, + new_size, ); return; } -- 2.54.0 From 769b5eaf9415f25cd68d6cd525614373f162ea90 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 37/94] 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 --- src/link/Coff.zig | 282 +++++++++++++++++++++++++++++----------------- 1 file changed, 181 insertions(+), 101 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7e7460207124e04993f78b2bec5dd9b496cf5633..45afcd72dff769810ebbcb0326690201cd5b20c6 100644 --- 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.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.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(), + ); - 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 }, + }, + ); + } - 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}); - 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); + 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.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 }, + }, + ); + } - 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, -- 2.54.0 From dc8dd5c08a4715c39de2e6ca8f4d4c6c4aa1f96b Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 38/94] Coff: more debug output - Track non-global input symbol names, for use in debug output and error messages - Output COMDAT section names where possible --- src/link/Coff.zig | 142 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 34 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 45afcd72dff769810ebbcb0326690201cd5b20c6..beffbdce5d470fbca7d743ba0561011086309fc0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -44,7 +44,7 @@ pending_default_libs: std.ArrayList(struct { }), alternate_names: std.AutoArrayHashMapUnmanaged(String, String), input_objects: std.ArrayList(InputObject), -input_symbols: std.ArrayList(Symbol.Index), +input_symbols: std.ArrayList(struct { si: Symbol.Index, name: String }), input_sections: std.ArrayList(Node.InputSection), input_section_pending_index: u32, inputs_complete: bool, @@ -300,6 +300,7 @@ pub const Node = union(enum) { const InputSection = struct { ioi: InputObject.Index, si: Symbol.Index, + comdat_si: Symbol.Index, file_location: MappedFile.Node.FileLocation, first_li: Node.InputSection.LocalIndex, crc: u32, @@ -918,9 +919,13 @@ pub const Symbol = struct { /// Relocations targeting this symbol target_relocs: Reloc.Index, section_number: SectionNumber, - /// Only used when outputting objects - sti: SymbolTable.Index, gmi: Node.GlobalMapIndex, + extra: union { + /// Only valid when outputting objects + sti: SymbolTable.Index, + /// Only valid when .ni == .input_section and .value_tag == .node_offset + isli: Node.InputSection.LocalIndex, + }, pub const DllStorageClass = enum(u2) { default, @@ -1062,7 +1067,7 @@ pub const Symbol = struct { pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); - const index = sym.sti.unwrap() orelse return; + const index = sym.extra.sti.unwrap() orelse return; var ri = sym.target_relocs; while (ri != .none) { const reloc = ri.get(coff); @@ -2496,7 +2501,7 @@ pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.c } pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) *align(2) std.coff.SectionDefinition { - const sti = si.get(coff).sti; + const sti = si.get(coff).extra.sti; const entry = symbolTableEntryPtr(coff, sti).?; assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1); return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); @@ -2546,8 +2551,8 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, - .sti = .none, .gmi = .none, + .extra = .{ .sti = .none }, }; return @enumFromInt(coff.symbols.items.len); } @@ -2973,7 +2978,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void const sym = si.get(coff); assert(sym.ni != .none or sym.gmi != .none); - const entry = coff.symbolTableEntryPtr(sym.sti) orelse entry: { + const entry = coff.symbolTableEntryPtr(sym.extra.sti) orelse entry: { var buf: [15]u8 = undefined; const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { @@ -3038,10 +3043,10 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); - sym.sti = .wrap(old_num_symbols); + sym.extra = .{ .sti = .wrap(old_num_symbols) }; si.flushSymbolTableIndex(coff); - const entry = coff.symbolTableEntryPtr(sym.sti).?; + const entry = coff.symbolTableEntryPtr(sym.extra.sti).?; symbol_name.store(coff, &entry.name); entry.section_number = @enumFromInt(@intFromEnum(sym.section_number)); @@ -3071,7 +3076,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }, }); - log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.sti }); + log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.extra.sti }); } fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void { @@ -3467,8 +3472,8 @@ pub fn addReloc( else => |loc_sn| sri: { // The target may not have a node yet, or it could be an extern that will never // have a node. In that case, flushGlobal will create the symbol table entry. - const sti: SymbolTable.Index = if (target.sti != .none) - target.sti + const sti: SymbolTable.Index = if (target.extra.sti != .none) + target.extra.sti else if (target.ni != .none) sti: { try coff.pendingSymbolTableEntry(target_si); break :sti .none; @@ -4381,6 +4386,10 @@ fn loadObject( }, .first_li = @enumFromInt(coff.input_symbols.items.len), .crc = section.comdat_crc, + .comdat_si = if (section.comdat_psi.unwrap()) |psi| + pending_symbols.values()[psi].si + else + .null, }; log.debug( @@ -4489,6 +4498,9 @@ fn loadObject( .weak_external_aux, => unreachable, } + + if (section.comdat_psi.unwrap() == @as(u32, @intCast(i))) + coff.getNode(section.si.get(coff).ni).input_section.inputSection(coff).comdat_si = symbol.si; } if (symbol.weak_external_psi.unwrap()) |weak_external_i| { @@ -4610,7 +4622,11 @@ fn loadObject( if (include_section) { assert(coff.getNode(symbol.si.get(coff).ni) == .input_section); - coff.input_symbols.addOneAssumeCapacity().* = symbol.si; + symbol.si.get(coff).extra = .{ .isli = @enumFromInt(coff.input_symbols.items.len) }; + coff.input_symbols.addOneAssumeCapacity().* = .{ + .si = symbol.si, + .name = symbol.name, + }; } } } @@ -5450,11 +5466,30 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { .input_section => |isi| { const other_ioi = isi.input(coff); if (loc_sym.gmi == .none) { - // TODO: We could report non-global names here if we intern them in loadObject - err.addNote("referenced by input '{f}{f}'", .{ - other_ioi.path(coff).fmtEscapeString(), - fmtMemberNameString(other_ioi.memberName(coff)), - }); + const section = isi.inputSection(coff); + const section_name = coff.getNode(loc_sym.ni.parent(&coff.mf)) + .object_section.name(coff).toSlice(coff); + + if (section.comdat_si != .null) { + const comdat_sym = section.comdat_si.get(coff); + const comdat_name = if (comdat_sym.gmi != .none) + comdat_sym.gmi.globalName(coff).name.toSlice(coff) + else + coff.input_symbols.items[@intFromEnum(comdat_sym.extra.isli)].name.toSlice(coff); + + err.addNote("referenced by input COMDAT section '{s}={s}' '{f}{f}'", .{ + section_name, + comdat_name, + other_ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(other_ioi.memberName(coff)), + }); + } else { + err.addNote("referenced by input section '{s}' '{f}{f}'", .{ + section_name, + other_ioi.path(coff).fmtEscapeString(), + fmtMemberNameString(other_ioi.memberName(coff)), + }); + } } else { err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{ loc_sym.gmi.globalName(coff).name.toSlice(coff), @@ -6558,9 +6593,9 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { }, .input_section => |isi| { isi.symbol(coff).flushMoved(coff); - for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |si| { - if (si.get(coff).ni != ni) break; - si.flushMoved(coff); + for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |input_symbol| { + if (input_symbol.si.get(coff).ni != ni) break; + input_symbol.si.flushMoved(coff); } }, .import_directory_table => coff.targetStore( @@ -7165,7 +7200,7 @@ pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpRe 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)); + try coff.printSymbol(w, tid, @enumFromInt(si)); return .enabled; } @@ -7183,10 +7218,15 @@ fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) !voi }); } -fn printSymbol(coff: *Coff, w: *Io.Writer, si: Symbol.Index) !void { +fn printSymbol( + coff: *Coff, + w: *Io.Writer, + tid: Zcu.PerThread.Id, + 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", .{ + try w.print("{d:0>6}@{d:0>2} {x:08} {s} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} ", .{ si, sym.section_number, if (sym.flags.value_tag == .size) @@ -7195,6 +7235,12 @@ fn printSymbol(coff: *Coff, w: *Io.Writer, si: Symbol.Index) !void { sym.ni.location(&coff.mf).resolve(&coff.mf)[1] else 0, + switch (sym.flags.value_tag) { + .alias_name => "an", + .alias_si => "as", + .node_offset => "no", + .size => "sz", + }, switch (sym.flags.type) { .unknown => "u", .code => "c", @@ -7204,8 +7250,15 @@ fn printSymbol(coff: *Coff, w: *Io.Writer, si: Symbol.Index) !void { if (sym.flags.value_tag == .node_offset) sym.value.node_offset else 0, node, sym.rva, - fmtGlobalName(coff, sym.gmi), }); + + if (sym.gmi != .none) { + try w.print("G {f}\n", .{fmtGlobalName(coff, sym.gmi)}); + } else { + try w.writeAll("| "); + try coff.printNodeName(w, tid, node); + try w.writeByte('\n'); + } } const FmtGlobalName = struct { coff: *Coff, gmi: Node.GlobalMapIndex }; @@ -7222,16 +7275,12 @@ fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error! try w.print("({s})", .{lib_name.toSlice(data.coff)}); } -pub fn printNode( +fn printNodeName( coff: *Coff, + w: *std.Io.Writer, tid: Zcu.PerThread.Id, - w: *Io.Writer, - ni: MappedFile.Node.Index, - indent: usize, + node: Node, ) !void { - const node = coff.getNode(ni); - try w.splatByteAll(' ', indent); - try w.writeAll(@tagName(node)); switch (node) { else => {}, .image_section => |si| try w.print("({s})", .{ @@ -7239,11 +7288,23 @@ pub fn printNode( }), .input_section => |isi| { const ioi = isi.input(coff); - try w.print("({f}{f}, {s})", .{ + const is = isi.inputSection(coff); + // TODO: Use only filename from these paths, they are long + try w.print("({f}{f}, {s}", .{ ioi.path(coff).fmtEscapeString(), fmtMemberNameString(ioi.memberName(coff)), - coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), + coff.getNode(is.si.node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }); + if (is.comdat_si != .null) { + const comdat_sym = is.comdat_si.get(coff); + const comdat_name = if (comdat_sym.gmi != .none) + comdat_sym.gmi.globalName(coff).name.toSlice(coff) + else + coff.input_symbols.items[@intFromEnum(comdat_sym.extra.isli)].name.toSlice(coff); + + try w.print("={s}", .{comdat_name}); + } + try w.writeAll(")"); }, .import_lookup_table, .import_address_table, @@ -7284,6 +7345,19 @@ pub fn printNode( }), }), } +} + +pub fn printNode( + coff: *Coff, + tid: Zcu.PerThread.Id, + w: *Io.Writer, + ni: MappedFile.Node.Index, + indent: usize, +) !void { + const node = coff.getNode(ni); + try w.splatByteAll(' ', indent); + try w.writeAll(@tagName(node)); + try coff.printNodeName(w, tid, node); { const mf_node = &coff.mf.nodes.items[@intFromEnum(ni)]; const off, const size = mf_node.location().resolve(&coff.mf); -- 2.54.0 From 8781abd79b5d8c8ce155b065c874d09076491e26 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 39/94] Coff: track the exports of a symbol so they can be updated Globals created as exports of other symbols were not being updated when the original symbol moved. Since we discover the entrypoint after exports are updated, nodes can move as a result of the entrypoint logic pulling in an input object. --- src/link/Coff.zig | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index beffbdce5d470fbca7d743ba0561011086309fc0..dbc2a81993d3ea6995aa0eb4b5bef538cccc5806 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -912,7 +912,8 @@ pub const Symbol = struct { dll_storage_class: DllStorageClass, // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - _: u8 = 0, + has_exports: bool, + _: u7 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -924,7 +925,11 @@ pub const Symbol = struct { /// Only valid when outputting objects sti: SymbolTable.Index, /// Only valid when .ni == .input_section and .value_tag == .node_offset + /// TODO: This is only used for name lookups, could just be String? isli: Node.InputSection.LocalIndex, + /// Only valid if flags.has_exports is set. The first in a contiguous + /// list of symbols that are exports of this symbol. + first_export_si: Symbol.Index, }, pub const DllStorageClass = enum(u2) { @@ -1063,6 +1068,15 @@ pub const Symbol = struct { sym.rva = coff.computeNodeRva(sym.ni) + sym.nodeOffset(coff); si.applyLocationRelocs(coff); si.applyTargetRelocs(coff, .none); + + if (sym.flags.has_exports) { + for (coff.symbols.items[@intFromEnum(sym.extra.first_export_si)..]) |*export_sym| { + if (export_sym.ni != sym.ni) break; + export_sym.rva = sym.rva; + const export_si: Symbol.Index = @enumFromInt(export_sym - coff.symbols.items.ptr); + export_si.applyTargetRelocs(coff, .none); + } + } } pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { @@ -2547,6 +2561,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .type = .unknown, .dll_storage_class = .default, .weak_external_strat = undefined, + .has_exports = false, }, .loc_relocs = .none, .target_relocs = .none, @@ -7063,9 +7078,14 @@ fn updateExportsInner( const machine = coff.targetLoad(&coff.headerPtr().machine); const exported_ni = exported_si.node(coff); const exported_sym = exported_si.get(coff); + exported_sym.extra = .{ .first_export_si = @enumFromInt(coff.symbols.items.len) }; + exported_sym.flags.has_exports = true; + for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); const name = @"export".opts.name.toSlice(ip); + // TODO: Add an errMsg if this conflicts with an existing global from an input + // first_export_si relies on this being a new symbol. const export_si = try coff.globalSymbol(.{ .name = name, .lib_name = null, -- 2.54.0 From 8a1376f5d001d79cb92dd67547c6a20661fe6bb4 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 40/94] Coff: fixes for .gnu - has_exports -> has_aliases, and use this for __(CTOR|DTOR)_LIST__ symbols, which weren't being updated properly - fix stale usage of sym ptr in flushGlobal --- src/link/Coff.zig | 458 +++++++++++++++++++++++----------------------- 1 file changed, 233 insertions(+), 225 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index dbc2a81993d3ea6995aa0eb4b5bef538cccc5806..9e91fa8787128f37df5e9d3a4e2716a30190e3fb 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -912,7 +912,7 @@ pub const Symbol = struct { dll_storage_class: DllStorageClass, // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - has_exports: bool, + has_aliases: bool, _: u7 = 0, }, /// Relocations contained within this symbol @@ -927,9 +927,9 @@ pub const Symbol = struct { /// Only valid when .ni == .input_section and .value_tag == .node_offset /// TODO: This is only used for name lookups, could just be String? isli: Node.InputSection.LocalIndex, - /// Only valid if flags.has_exports is set. The first in a contiguous - /// list of symbols that are exports of this symbol. - first_export_si: Symbol.Index, + /// Only valid if flags.has_aliases is set. The first in a contiguous + /// list of symbols that are aliases of this symbol. + first_alias_si: Symbol.Index, }, pub const DllStorageClass = enum(u2) { @@ -946,8 +946,8 @@ pub const Symbol = struct { const ValueTag = enum(u2) { node_offset, - alias_si, - alias_name, + weak_alias_si, + weak_alias_name, size, }; @@ -957,12 +957,12 @@ pub const Symbol = struct { node_offset: u32, /// This is a weak alias that can replace this symbol /// Globals only. - alias_si: Symbol.Index, + weak_alias_si: Symbol.Index, /// For weak externals that have an alias that is also an undef /// external, this is the name of the alias global that should /// be generated if this symbol is not resolved. /// Globals only. - alias_name: String, + weak_alias_name: String, /// The symbol size, or 0 if unknown size: u32, }; @@ -1069,8 +1069,8 @@ pub const Symbol = struct { si.applyLocationRelocs(coff); si.applyTargetRelocs(coff, .none); - if (sym.flags.has_exports) { - for (coff.symbols.items[@intFromEnum(sym.extra.first_export_si)..]) |*export_sym| { + if (sym.flags.has_aliases) { + for (coff.symbols.items[@intFromEnum(sym.extra.first_alias_si)..]) |*export_sym| { if (export_sym.ni != sym.ni) break; export_sym.rva = sym.rva; const export_si: Symbol.Index = @enumFromInt(export_sym - coff.symbols.items.ptr); @@ -2203,6 +2203,9 @@ pub fn initBuiltins(coff: *Coff) !void { ); const start_sym = start_osmi.symbol(coff).get(coff); + start_sym.extra = .{ .first_alias_si = @enumFromInt(coff.symbols.items.len) }; + start_sym.flags.has_aliases = true; + try start_sym.ni.resize(&coff.mf, gpa, addr_info.size); const start_slice = start_sym.ni.slice(&coff.mf); switch (addr_info.magic) { @@ -2561,7 +2564,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .type = .unknown, .dll_storage_class = .default, .weak_external_strat = undefined, - .has_exports = false, + .has_aliases = false, }, .loc_relocs = .none, .target_relocs = .none, @@ -4443,10 +4446,10 @@ fn loadObject( .external => { if (symbol.weak_external_psi.unwrap()) |weak_external_i| { // If the alias itself is an undef external, we need to wait until flushing the weak - // external global before creating a global for the alias, as another input - // could still provide the weak external. + // external global before creating a global for the alias, as another input could + // still provide the weak external. const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff); - weak_sym.setValue(.{ .alias_name = symbol.name }); + weak_sym.setValue(.{ .weak_alias_name = symbol.name }); weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux; } @@ -4476,9 +4479,9 @@ fn loadObject( alias.weak_external_psi = .wrap(@intCast(i)); } else { sym.setValue(if (alias.si.unwrap()) |alias_si| .{ - .alias_si = alias_si, + .weak_alias_si = alias_si, } else .{ - .alias_name = alias.name, + .weak_alias_name = alias.name, }); sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux; } @@ -4521,7 +4524,7 @@ fn loadObject( if (symbol.weak_external_psi.unwrap()) |weak_external_i| { assert(symbol.si != .null); const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff); - weak_sym.setValue(.{ .alias_si = symbol.si }); + weak_sym.setValue(.{ .weak_alias_si = symbol.si }); weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux; } @@ -5985,17 +5988,16 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const gpa = comp.gpa; const gn = gmi.globalName(coff); const si = gmi.symbol(coff); - const sym = si.get(coff); const is_late = gmi.unwrap().? < coff.global_pending_index; log.debug( "flushGlobal({s}, {?s}, {}) = n{d} {d}@{d}", - .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), is_late, sym.ni, si, sym.section_number }, + .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), is_late, si.get(coff).ni, si, si.get(coff).section_number }, ); if (!coff.isImage()) { try coff.pendingSymbolTableEntry(si); - if (coff.isArchive() and sym.ni != .none) + if (coff.isArchive() and si.get(coff).ni != .none) try coff.ensureMemberSymbol( coff.getNode(Node.known.zcu_member).archive_member, gn.name, @@ -6004,6 +6006,9 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; } + if (si.get(coff).ni != .none) + return true; + const Import = struct { lib_name: String, name: String.Optional, @@ -6014,7 +6019,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }, }; - const opt_import: ?Import = if (sym.ni == .none) import: { + const import: Import = import: { + const sym = si.get(coff); const global_name = gn.name.toSlice(coff); const imp_match = std.mem.startsWith(u8, global_name, imp_prefix); @@ -6030,7 +6036,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const opt_alt_search_name = coff.alternate_names.get(search_name); const search_libs = if (is_late) switch (sym.flags.value_tag) { - .alias_si, .alias_name => switch (sym.flags.weak_external_strat) { + .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) { .no_library => false, .library, .alias, @@ -6044,7 +6050,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { else => true, } else search_libs: { if (switch (sym.flags.value_tag) { - .alias_si, .alias_name => true, + .weak_alias_si, .weak_alias_name => true, else => opt_alt_search_name != null, }) { // We need to wait until all exports are known before resolving these @@ -6137,16 +6143,18 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { } switch (sym.flags.value_tag) { - .alias_si => { + .weak_alias_si => { assert(is_late); - try coff.aliasGlobal(gmi, sym.value.alias_si); + try coff.aliasGlobal(gmi, sym.value.weak_alias_si); return true; }, - .alias_name => { + .weak_alias_name => { assert(is_late); // Convert an unresolved weak external that itself refers to an undef external // into a (possibly new) global, so it can be resolved separately. - const alias_gop = try coff.getOrPutGlobalSymbol(.{ .name = sym.value.alias_name.toSlice(coff) }); + const alias_gop = try coff.getOrPutGlobalSymbol(.{ + .name = sym.value.weak_alias_name.toSlice(coff), + }); try coff.aliasGlobal(gmi, alias_gop.value_ptr.*); return true; }, @@ -6174,216 +6182,216 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }; } - break :import null; - } else null; + return true; + }; - if (opt_import) |import| { - assert(sym.ni == .none); - const lib_name = import.lib_name.toSlice(coff); + const lib_name = import.lib_name.toSlice(coff); - try coff.nodes.ensureUnusedCapacity(gpa, 4); - try coff.symbols.ensureUnusedCapacity(gpa, 1); + try coff.nodes.ensureUnusedCapacity(gpa, 4); + try coff.symbols.ensureUnusedCapacity(gpa, 1); - const target_endian = coff.targetEndian(); - const addr_info = coff.targetAddrInfo(); - const gop = try coff.import_table.entries.getOrPutAdapted( + const sym = si.get(coff); + const target_endian = coff.targetEndian(); + const addr_info = coff.targetAddrInfo(); + const gop = try coff.import_table.entries.getOrPutAdapted( + gpa, + lib_name, + ImportTable.Adapter{ .coff = coff }, + ); + const import_hint_name_align: std.mem.Alignment = .@"2"; + if (!gop.found_existing) { + errdefer _ = coff.import_table.entries.pop(); + try coff.import_table.ni.resize( + &coff.mf, gpa, - lib_name, - ImportTable.Adapter{ .coff = coff }, + @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2), ); - const import_hint_name_align: std.mem.Alignment = .@"2"; - if (!gop.found_existing) { - errdefer _ = coff.import_table.entries.pop(); - try coff.import_table.ni.resize( - &coff.mf, - gpa, - @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2), + const import_hint_name_table_len = + import_hint_name_align.forward(lib_name.len + ".dll".len + 1); + const idata_section_ni = coff.import_table.ni.parent(&coff.mf); + const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ + .size = addr_info.size * 2, + .alignment = addr_info.alignment, + .moved = true, + }); + const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ + .size = addr_info.size * 2, + .alignment = addr_info.alignment, + .moved = true, + }); + const import_address_table_si = coff.addSymbolAssumeCapacity(); + { + const import_address_table_sym = import_address_table_si.get(coff); + import_address_table_sym.ni = import_address_table_ni; + assert(import_address_table_sym.loc_relocs == .none); + import_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + import_address_table_sym.section_number = + coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number; + } + const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ + .size = import_hint_name_table_len, + .alignment = import_hint_name_align, + .moved = true, + }); + gop.value_ptr.* = .{ + .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), + }; + const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf); + @memcpy(import_hint_name_slice[0..lib_name.len], lib_name); + @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll"); + @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0); + coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @enumFromInt(gop.index) }); + coff.nodes.appendAssumeCapacity(.{ .import_address_table = @enumFromInt(gop.index) }); + coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @enumFromInt(gop.index) }); + + const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2]; + import_directory_entries.* = .{ .{ + .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni), + .time_date_stamp = 0, + .forwarder_chain = 0, + .name_rva = coff.computeNodeRva(import_hint_name_table_ni), + .import_address_table_rva = coff.computeNodeRva(import_address_table_ni), + }, .{ + .import_lookup_table_rva = 0, + .time_date_stamp = 0, + .forwarder_chain = 0, + .name_rva = 0, + .import_address_table_rva = 0, + } }; + if (target_endian != native_endian) + std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries); + } + + log.debug( + "flushGlobalImport({s}, {?s}, {d}, {s})", + .{ gn.name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name }, + ); + + const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{ + .iti = @enumFromInt(gop.index), + .name = import.name, + .ordinal_hint = import.ordinal_hint, + }); + if (!iat_symbol_gop.found_existing) { + const import_symbol_index = gop.value_ptr.len; + iat_symbol_gop.value_ptr.* = import_symbol_index; + + 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), ); - const import_hint_name_table_len = - import_hint_name_align.forward(lib_name.len + ".dll".len + 1); - const idata_section_ni = coff.import_table.ni.parent(&coff.mf); - const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ - .size = addr_info.size * 2, - .alignment = addr_info.alignment, - .moved = true, - }); - const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ - .size = addr_info.size * 2, - .alignment = addr_info.alignment, - .moved = true, - }); - const import_address_table_si = coff.addSymbolAssumeCapacity(); - { - const import_address_table_sym = import_address_table_si.get(coff); - import_address_table_sym.ni = import_address_table_ni; - assert(import_address_table_sym.loc_relocs == .none); - import_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - import_address_table_sym.section_number = - coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number; - } - const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{ - .size = import_hint_name_table_len, - .alignment = import_hint_name_align, - .moved = true, - }); - gop.value_ptr.* = .{ - .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), - }; - const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf); - @memcpy(import_hint_name_slice[0..lib_name.len], lib_name); - @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll"); - @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0); - coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @enumFromInt(gop.index) }); - coff.nodes.appendAssumeCapacity(.{ .import_address_table = @enumFromInt(gop.index) }); - coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @enumFromInt(gop.index) }); - - const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2]; - import_directory_entries.* = .{ .{ - .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni), - .time_date_stamp = 0, - .forwarder_chain = 0, - .name_rva = coff.computeNodeRva(import_hint_name_table_ni), - .import_address_table_rva = coff.computeNodeRva(import_address_table_ni), - }, .{ - .import_lookup_table_rva = 0, - .time_date_stamp = 0, - .forwarder_chain = 0, - .name_rva = 0, - .import_address_table_rva = 0, - } }; - if (target_endian != native_endian) - std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries); - } - - log.debug( - "flushGlobalImport({s}, {?s}, {d}, {s})", - .{ gn.name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name }, - ); - - const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{ - .iti = @enumFromInt(gop.index), - .name = import.name, - .ordinal_hint = import.ordinal_hint, - }); - if (!iat_symbol_gop.found_existing) { - const import_symbol_index = gop.value_ptr.len; - iat_symbol_gop.value_ptr.* = import_symbol_index; - - 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; - - 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])); - ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian); - @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_name.?.len], opt_name.?); - @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_name.?.len ..], 0); - break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index; - } else 0; - - const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf); - const import_address_slice = import_address_table_ni.slice(&coff.mf); - switch (addr_info.magic) { - _ => unreachable, - inline .PE32, .@"PE32+" => |ct_magic| { - 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 = .{ - .{ - .payload = if (import.name == .none) - .{ .ordinal = .{ .ordinal = import.ordinal_hint } } - else - .{ .hint_name_rva = @intCast(import_hint_name_rva) }, - .is_ordinal = import.name == .none, - }, - @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)), - }; - if (native_endian != target_endian) - for (import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v); - - import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas; - import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas; - }, - } - } - - assert(sym.loc_relocs == .none); - const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*); - switch (import.kind) { - .iat_ptr => { - const iat_sym = gop.value_ptr.import_address_table_si.get(coff); - sym.section_number = iat_sym.section_number; - 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; - sym.loc_relocs = @enumFromInt(coff.relocs.items.len); - switch (coff.targetLoad(&coff.headerPtr().machine)) { - else => |tag| @panic(@tagName(tag)), - .AMD64 => { - const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 }; - const target = &comp.root_mod.resolved_target.result; - const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{ - .alignment = switch (comp.root_mod.optimize_mode) { - .Debug, - .ReleaseSafe, - .ReleaseFast, - => target_util.defaultFunctionAlignment(target), - .ReleaseSmall => target_util.minFunctionAlignment(target), - }.toStdMem(), - .size = init.len, - }); - @memcpy(ni.slice(&coff.mf)[0..init.len], &init); - sym.ni = ni; - sym.setValue(.{ .size = init.len }); - try coff.addReloc( - si, - init.len - 4, - gop.value_ptr.import_address_table_si, - .{ .known = iat_offset }, - .{ .AMD64 = .REL32 }, - ); + 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; + + 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])); + ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian); + @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_name.?.len], opt_name.?); + @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_name.?.len ..], 0); + break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index; + } else 0; + + const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf); + const import_address_slice = import_address_table_ni.slice(&coff.mf); + switch (addr_info.magic) { + _ => unreachable, + inline .PE32, .@"PE32+" => |ct_magic| { + 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 = .{ + .{ + .payload = if (import.name == .none) + .{ .ordinal = .{ .ordinal = import.ordinal_hint } } + else + .{ .hint_name_rva = @intCast(import_hint_name_rva) }, + .is_ordinal = import.name == .none, }, - } - coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi }); - sym.rva = coff.computeNodeRva(sym.ni); - si.applyLocationRelocs(coff); + @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)), + }; + if (native_endian != target_endian) + for (import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v); + + import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas; + import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas; }, } } + assert(sym.loc_relocs == .none); + const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*); + switch (import.kind) { + .iat_ptr => { + const iat_sym = gop.value_ptr.import_address_table_si.get(coff); + sym.section_number = iat_sym.section_number; + 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; + sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + switch (coff.targetLoad(&coff.headerPtr().machine)) { + else => |tag| @panic(@tagName(tag)), + .AMD64 => { + const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 }; + const target = &comp.root_mod.resolved_target.result; + const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{ + .alignment = switch (comp.root_mod.optimize_mode) { + .Debug, + .ReleaseSafe, + .ReleaseFast, + => target_util.defaultFunctionAlignment(target), + .ReleaseSmall => target_util.minFunctionAlignment(target), + }.toStdMem(), + .size = init.len, + }); + @memcpy(ni.slice(&coff.mf)[0..init.len], &init); + sym.ni = ni; + sym.setValue(.{ .size = init.len }); + try coff.addReloc( + si, + init.len - 4, + gop.value_ptr.import_address_table_si, + .{ .known = iat_offset }, + .{ .AMD64 = .REL32 }, + ); + }, + } + coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi }); + sym.rva = coff.computeNodeRva(sym.ni); + si.applyLocationRelocs(coff); + }, + } + return true; } 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()) return .none; + const gpa = comp.gpa; + const machine = coff.targetLoad(&coff.headerPtr().machine); + const target = &comp.root_mod.resolved_target.result; + return next: switch (pending) { .entry => { // TODO: Use explicitly specified entry if set, add err if not found @@ -6402,7 +6410,7 @@ fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol { .{ "wWinMainCRTStartup", "wWinMainCRTStartup" }, } else - &.{.{ null, "_DllMainCRTStartup" }}; + &.{.{ null, if (target.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup" }}; const entry_si = for (entries) |entry| { if (entry[0]) |required_name| @@ -7073,13 +7081,13 @@ fn updateExportsInner( Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu), ))), }; - while (try coff.idle(pt.tid)) {} + while (try coff.idle(pt.tid)) {} // TODO: Is this necessary now that we handle exports moving via has_aliases? const machine = coff.targetLoad(&coff.headerPtr().machine); const exported_ni = exported_si.node(coff); const exported_sym = exported_si.get(coff); - exported_sym.extra = .{ .first_export_si = @enumFromInt(coff.symbols.items.len) }; - exported_sym.flags.has_exports = true; + exported_sym.extra = .{ .first_alias_si = @enumFromInt(coff.symbols.items.len) }; + exported_sym.flags.has_aliases = true; for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); @@ -7256,8 +7264,8 @@ fn printSymbol( else 0, switch (sym.flags.value_tag) { - .alias_name => "an", - .alias_si => "as", + .weak_alias_name => "an", + .weak_alias_si => "as", .node_offset => "no", .size => "sz", }, -- 2.54.0 From 77ccbc7ceeae57e7c0b089503f1c7873a88342f4 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 41/94] Coff: entry point fixup --- src/link/Coff.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 9e91fa8787128f37df5e9d3a4e2716a30190e3fb..ea8adf82bd26e115941622b857a723d997bde21b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -6410,7 +6410,13 @@ fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol { .{ "wWinMainCRTStartup", "wWinMainCRTStartup" }, } else - &.{.{ null, if (target.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup" }}; + &.{.{ + null, + if (comp.config.link_libc and target.abi.isGnu()) + "DllMainCRTStartup" + else + "_DllMainCRTStartup", + }}; const entry_si = for (entries) |entry| { if (entry[0]) |required_name| -- 2.54.0 From 2d3d3c042e99da64df53a533852bd0e27166b4f6 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 42/94] tests: standalone/shared_library runs on all permutations of link_libc and use_llvm --- test/standalone/shared_library/build.zig | 47 +++++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 2fa0bfdfd694332fce62543a8de8fe30b93ef311..478fcd501704cacb21081cc7f463fe8fc1a386c3 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -7,16 +7,42 @@ pub fn build(b: *std.Build) void { const optimize: std.builtin.OptimizeMode = .Debug; const target = b.standardTargetOptions(.{}); - const exe_names: []const []const u8 = &.{ "test", "test-dync", "test-no-llvm", "test-no-llvm-dync" }; - const lib_names: []const []const u8 = &.{ "mathtest", "mathtest-dync", "mathtest-no-llvm", "mathtest-no-llvm-dync" }; - const lib_link_libc: []const bool = &.{ false, true, false, true }; - const lib_use_llvm: []const bool = &.{ true, true, false, false }; + const exe_names: []const []const u8 = &.{ + "test", + "test-dync", + "test-no-llvm", + "test-no-llvm-dync", + "test-exe-no-llvm", + "test-dync-exe-no-llvm", + "test-no-llvm-exe-no-llvm", + "test-no-llvm-dync-exe-no-llvm", + }; + const lib_names: []const []const u8 = &.{ + "mathtest", + "mathtest-dync", + "mathtest-no-llvm", + "mathtest-no-llvm-dync", + "mathtest-exe-no-llvm", + "mathtest-dync-exe-no-llvm", + "mathtest-no-llvm-exe-no-llvm", + "mathtest-no-llvm-dync-exe-no-llvm", + }; + const lib_link_libc: []const bool = &.{ false, true, false, true, false, true, false, true }; + const lib_use_llvm: []const bool = &.{ true, true, false, false, true, true, false, false }; + const exe_use_llvm: []const bool = &.{ true, true, true, true, false, false, false, false }; - for (exe_names, lib_names, lib_link_libc, lib_use_llvm) |exe_name, lib_name, dyn_libc, use_llvm| { - if (!use_llvm and target.result.os.tag == .macos) continue; // TODO: Library not loaded: @rpath/libmathtest-no-llvm.dylib (segment '__CONST_ZIG' vm address out of order) - if (!use_llvm and target.result.os.tag == .freebsd) continue; // TODO: Shared object "libmathtest-no-llvm.so.1" not found - if (!use_llvm and target.result.os.tag == .netbsd) continue; // TODO: Shared object "libmathtest-no-llvm.so.1" not found - if (!use_llvm and target.result.os.tag == .openbsd) continue; // TODO: duplicate symbol definition: atexit + for ( + exe_names, + lib_names, + lib_link_libc, + lib_use_llvm, + exe_use_llvm, + ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| { + const use_llvm = lib_llvm or exe_llvm; + if (!use_llvm and target.result.os.tag == .macos) continue; // TODO + if (!use_llvm and target.result.os.tag == .freebsd) continue; // TODO + if (!use_llvm and target.result.os.tag == .netbsd) continue; // TODO + if (!use_llvm and target.result.os.tag == .openbsd) continue; // TODO if (!use_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO if (!use_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO if (!use_llvm and target.result.cpu.arch == .s390x) continue; // TODO @@ -31,7 +57,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = dyn_libc, }), - .use_llvm = use_llvm, + .use_llvm = lib_llvm, }); const exe = b.addExecutable(.{ @@ -42,6 +68,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = true, }), + .use_llvm = exe_llvm, }); exe.root_module.addCSourceFile(.{ .file = b.path("test.c"), -- 2.54.0 From 2f8a3eb4c8f2de68f9337978ab9b628087530c34 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 43/94] Coff: fixup alias list - Change aliases to a linked list, as they aren't always contiguous --- src/link/Coff.zig | 70 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ea8adf82bd26e115941622b857a723d997bde21b..63933fa5599712a9be3c1ab133403630fec78bb7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -912,7 +912,7 @@ pub const Symbol = struct { dll_storage_class: DllStorageClass, // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - has_aliases: bool, + has_alias: bool, _: u7 = 0, }, /// Relocations contained within this symbol @@ -927,9 +927,9 @@ pub const Symbol = struct { /// Only valid when .ni == .input_section and .value_tag == .node_offset /// TODO: This is only used for name lookups, could just be String? isli: Node.InputSection.LocalIndex, - /// Only valid if flags.has_aliases is set. The first in a contiguous - /// list of symbols that are aliases of this symbol. - first_alias_si: Symbol.Index, + /// Only valid if flags.has_alias is set. + /// The next symbol in the list of aliases of this symbol. + next_alias_si: Symbol.Index, }, pub const DllStorageClass = enum(u2) { @@ -1069,13 +1069,13 @@ pub const Symbol = struct { si.applyLocationRelocs(coff); si.applyTargetRelocs(coff, .none); - if (sym.flags.has_aliases) { - for (coff.symbols.items[@intFromEnum(sym.extra.first_alias_si)..]) |*export_sym| { - if (export_sym.ni != sym.ni) break; - export_sym.rva = sym.rva; - const export_si: Symbol.Index = @enumFromInt(export_sym - coff.symbols.items.ptr); - export_si.applyTargetRelocs(coff, .none); - } + var alias_sym = sym; + while (alias_sym.flags.has_alias) { + const alias_si = alias_sym.extra.next_alias_si; + alias_sym = alias_si.get(coff); + assert(alias_sym.ni == sym.ni); + alias_sym.rva = sym.rva; + alias_si.applyTargetRelocs(coff, .none); } } @@ -2028,9 +2028,9 @@ fn initHeaders( try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); assert(coff.addSymbolAssumeCapacity() == .null); - // TODO: How do we tell MappedFile not to allocate physical space for these? - // TODO: Could have a node flag 'virtual' that can never have slice* called on it or fileLocation - // TODO: Instead of it's own section, we can place .bss as a pseudo-section at the end of .text in the extra space + // TODO: How do we tell MappedFile not to allocate physical space for .bss? + // TODO: Could have a node flag 'virtual' that can never have slice* or fileLocation called on it + // TODO: Instead of it's own section, place .bss as a pseudo-section at the end of .text in the extra space assert(try coff.addSection(.@".bss", .{ .CNT_UNINITIALIZED_DATA = true, .MEM_READ = true, @@ -2203,9 +2203,6 @@ pub fn initBuiltins(coff: *Coff) !void { ); const start_sym = start_osmi.symbol(coff).get(coff); - start_sym.extra = .{ .first_alias_si = @enumFromInt(coff.symbols.items.len) }; - start_sym.flags.has_aliases = true; - try start_sym.ni.resize(&coff.mf, gpa, addr_info.size); const start_slice = start_sym.ni.slice(&coff.mf); switch (addr_info.magic) { @@ -2229,6 +2226,9 @@ pub fn initBuiltins(coff: *Coff) !void { const list_sym = list_si.get(coff); list_sym.ni = start_sym.ni; list_sym.section_number = start_sym.section_number; + + start_sym.extra = .{ .next_alias_si = list_si }; + start_sym.flags.has_alias = true; } } } @@ -2564,7 +2564,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .type = .unknown, .dll_storage_class = .default, .weak_external_strat = undefined, - .has_aliases = false, + .has_alias = false, }, .loc_relocs = .none, .target_relocs = .none, @@ -2692,10 +2692,6 @@ fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index { pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { const gop = try coff.getOrPutGlobalSymbol(opts); - if (gop.found_existing) { - // TODO: Need to know if this is an export or extern, in order to decide if this is duplicate, add to opts - } - return gop.value_ptr.*; } @@ -3525,7 +3521,6 @@ pub fn addReloc( ); // TODO: These need to allocate from a free list (once deleting relocs is supported) (or can we just remove swap?) - const sri: Section.RelocationIndex = .wrap(old_num_relocations); const entry = sri.entry(coff, loc_sn).?; if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index); @@ -3826,7 +3821,7 @@ fn loadObject( try member.initHeader(coff, path_str, header.time_date_stamp); { - // TODO: This could be deferred to an idle task? + // TODO: This should be deferred to an idle task var nw: MappedFile.Node.Writer = undefined; member.content_ni.writer(&coff.mf, gpa, &nw); defer nw.deinit(); @@ -4101,7 +4096,7 @@ fn loadObject( if (arg.len == 0) continue; if (std.ascii.startsWithIgnoreCase(arg, "-exclude-symbols:")) { - // TODO: When implementing mingw auto-exports (if at all?), use this to not export this symbol + // TODO: When implementing mingw auto-exports (if at all?), track this to not export this symbol } else if (std.ascii.startsWithIgnoreCase(arg, "/include:")) { _ = try coff.globalSymbol(.{ .name = arg["/include:".len..] }); } else if (std.ascii.startsWithIgnoreCase(arg, "/alternatename:")) { @@ -4251,7 +4246,7 @@ fn loadObject( .lib_name = null, }); - // TODO: What if the same symbol defined twice in this obj? + // TODO: What if the same symbol is incorrectly defined twice in this obj? // TODO: Would need to mark this global as pending, or notice it later when .ni != none if (!global_gop.found_existing or global_gop.value_ptr.get(coff).ni == .none) { symbol.si = global_gop.value_ptr.*; @@ -4319,7 +4314,7 @@ fn loadObject( .LARGEST => { // TODO: Resize existing .ni and replace with this section's contents // TODO: This will be tricky, what to do about existing InputSection? - unreachable; // TODO + unreachable; }, .NONE, .ASSOCIATIVE, _ => unreachable, } @@ -4504,7 +4499,7 @@ fn loadObject( symbol.si = coff.addSymbolAssumeCapacity(); }, .external => { - // TODO: Assert this is not the comdat leader + assert(index != section.comdat_psi.unwrap()); const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); symbol.si = global_gop.value_ptr.*; @@ -5017,7 +5012,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo } else { member.content.object.size = res.size; // TODO: If .UNKNOWN assert later that it contains no non-undef symbols? - // Microsoft's CRT contains members that set .UNKNOWN but do have symbols + // Microsoft's CRT contains members that set .UNKNOWN but do have undef symbols if (machine != expected_machine and machine != .UNKNOWN) { return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{ res.name, @@ -5192,7 +5187,6 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde si.applyLocationRelocs(coff); } - // TODO: Did my MappedFile resize change affect this? if (nav.resolved.?.@"linksection".unwrap()) |_| { try ni.resize(&coff.mf, gpa, si.get(coff).value.size); var parent_ni = ni; @@ -5746,7 +5740,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; } - // TODO: Idle task for flushing obj into lib? + // TODO: Idle task for flushing obj into lib if (coff.input_section_pending_index < coff.input_sections.items.len) { const isi: Node.InputSection.Index = @enumFromInt(coff.input_section_pending_index); coff.input_section_pending_index += 1; @@ -7087,14 +7081,11 @@ fn updateExportsInner( Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu), ))), }; - while (try coff.idle(pt.tid)) {} // TODO: Is this necessary now that we handle exports moving via has_aliases? const machine = coff.targetLoad(&coff.headerPtr().machine); const exported_ni = exported_si.node(coff); const exported_sym = exported_si.get(coff); - exported_sym.extra = .{ .first_alias_si = @enumFromInt(coff.symbols.items.len) }; - exported_sym.flags.has_aliases = true; - + var prev_alias_si = exported_si; for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); const name = @"export".opts.name.toSlice(ip); @@ -7111,6 +7102,12 @@ fn updateExportsInner( export_sym.section_number = exported_sym.section_number; defer export_si.applyTargetRelocs(coff, .none); + const prev_alias_sym = prev_alias_si.get(coff); + assert(!prev_alias_sym.flags.has_alias); + prev_alias_sym.extra = .{ .next_alias_si = export_si }; + prev_alias_sym.flags.has_alias = true; + prev_alias_si = export_si; + if (!coff.isImage()) continue; const entries_ctx = ExportTable.Adapter{ .coff = coff }; @@ -7153,7 +7150,8 @@ fn updateExportsInner( coff.targetStore(&edt.number_of_names, @intCast(export_count)); edt.number_of_entries = edt.number_of_names; - // TODO: These should all be resized ahead of time to fit all exports (after https://github.com/ziglang/zig/issues/23616) + // TODO: These should all be resized ahead of time to fit all exports + // after https://github.com/ziglang/zig/issues/23616 try coff.export_table.export_address_table_si.node(coff).resize( &coff.mf, gpa, -- 2.54.0 From 90303a7b619ee104133089fc3cf74674d03bf42e Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 44/94] - Fixup for new prelink task API --- src/Compilation.zig | 7 ++++++- src/link.zig | 2 +- src/link/Coff.zig | 9 ++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index b5c74b8ad8abdba4412fbcae4092e9e794b0e536..527e71a8994aaf430e080a6d436cdda7f0b8c4cf 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5402,7 +5402,12 @@ fn buildMingwImportLib(comp: *Compilation, lib_name: []const u8, is_prelink: boo }; if (is_prelink) - comp.queuePrelinkTasks(&.{.{ .load_archive = crt_file_path }}) catch |err| comp.lockAndSetMiscFailure( + comp.queuePrelinkTasks(&.{.{ + .load_archive = .{ + .path = crt_file_path, + .must_link = false, + }, + }}) catch |err| comp.lockAndSetMiscFailure( .windows_import_lib, "unable to queue prelink task for mingw import lib {f}: {t}", .{ crt_file_path, err }, diff --git a/src/link.zig b/src/link.zig index 8d3203df40936d99bf179097e92fdf497484573d..6352c03b0282e8ebadf26234dce2228673e42195 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1568,7 +1568,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }) catch return diags.setAllocFailure(), ); if (std.mem.endsWith(u8, lib.name, "lib")) { - base.openLoadArchive(path, null) catch |err| switch (err) { + base.openLoadArchive(path, false) catch |err| switch (err) { error.LinkFailure => return, // error reported via diags else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 63933fa5599712a9be3c1ab133403630fec78bb7..5c48b4beceacc8142c8ad1a4168faeee719a791e 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -6404,13 +6404,7 @@ fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol { .{ "wWinMainCRTStartup", "wWinMainCRTStartup" }, } else - &.{.{ - null, - if (comp.config.link_libc and target.abi.isGnu()) - "DllMainCRTStartup" - else - "_DllMainCRTStartup", - }}; + &.{.{ null, if (target.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup" }}; const entry_si = for (entries) |entry| { if (entry[0]) |required_name| @@ -7081,6 +7075,7 @@ fn updateExportsInner( Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu), ))), }; + while (try coff.idle(pt.tid)) {} const machine = coff.targetLoad(&coff.headerPtr().machine); const exported_ni = exported_si.node(coff); -- 2.54.0 From 48869bec3d39af0394c90cf6f7e67e656d26eb2b Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:35 -0400 Subject: [PATCH 45/94] MappedFile: add some basic tests --- src/link/MappedFile.zig | 170 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 9363e11a2cfc16c7f09471958ecd226ce77d40c7..79624f1f0967c4acecb72b55a069af2ddb181f79 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1337,3 +1337,173 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void { ni = node.next; } } + +const testing = std.testing; +fn testVerifyContent(mf: *@This(), ni: Node.Index, value: u8, init_len: usize) !void { + // Not using std.mem.allEqual, so we can get useful output + const slice = ni.slice(mf); + var buf: [256]u8 = undefined; + @memset(buf[0..init_len], value); + @memset(buf[init_len..], 0); + try testing.expectEqualSlices(u8, buf[0..slice.len], slice); +} + +test { + const gpa = testing.allocator; + + var tmp_dir = testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + + var file = try tmp_dir.dir.createFile(testing.io, "test.mf", .{ .read = true }); + defer file.close(testing.io); + + var mf = try init(file, gpa, testing.io); + defer mf.deinit(gpa); + + const a = try mf.addFirstChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" }); + const c = try mf.addLastChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" }); + const b = try mf.addNodeAfter(gpa, a, .{ .fixed = true, .alignment = .@"4" }); + const d = try mf.addNodeAfter(gpa, b, .{ .alignment = .@"4" }); + + const a_init_size = 8; + const b_init_size = 16; + const c_init_size = 24; + const d_init_size = 28; + + // Resize without content + { + // Verify size is aligned forward + try d.resize(&mf, gpa, d_init_size - 1); + try a.resize(&mf, gpa, a_init_size - 2); + try c.resize(&mf, gpa, c_init_size); + try b.resize(&mf, gpa, b_init_size); + mf.verify(); + + const a_loc, const a_size = a.location(&mf).resolve(&mf); + const b_loc, const b_size = b.location(&mf).resolve(&mf); + const c_loc, const c_size = c.location(&mf).resolve(&mf); + _, const d_size = d.location(&mf).resolve(&mf); + try testing.expect(a_size >= a_init_size); + try testing.expect(b_size >= b_init_size); + try testing.expect(c_size >= c_init_size); + try testing.expect(d_size >= d_init_size); + try testing.expect(b_loc >= a_loc + a_size); + try testing.expect(c_loc >= b_loc + b_size); + } + + const a_exp_size = 24; + const b_exp_size = 28; + const c_exp_size = 48; + const d_exp_size = 32; + + // Resize with content + { + @memset(a.slice(&mf)[0..a_init_size], 0xaa); + @memset(b.slice(&mf)[0..b_init_size], 0xbb); + @memset(c.slice(&mf)[0..c_init_size], 0xcc); + @memset(d.slice(&mf)[0..d_init_size], 0xdd); + + try a.resize(&mf, gpa, a_exp_size); + try b.resize(&mf, gpa, b_exp_size); + try c.resize(&mf, gpa, c_exp_size); + try d.resize(&mf, gpa, d_exp_size); + mf.verify(); + + const a_loc, const a_size = a.location(&mf).resolve(&mf); + const b_loc, const b_size = b.location(&mf).resolve(&mf); + const c_loc, const c_size = c.location(&mf).resolve(&mf); + _, const d_size = d.location(&mf).resolve(&mf); + try testing.expect(a_size >= a_exp_size); + try testing.expect(b_size >= b_exp_size); + try testing.expect(c_size >= c_exp_size); + try testing.expect(d_size >= d_exp_size); + try testing.expect(b_loc >= a_loc + a_size); + try testing.expect(c_loc >= b_loc + b_size); + + try testVerifyContent(&mf, a, 0xaa, a_init_size); + try testVerifyContent(&mf, b, 0xbb, b_init_size); + try testVerifyContent(&mf, c, 0xcc, c_init_size); + try testVerifyContent(&mf, d, 0xdd, d_init_size); + } + + // Re-align nodes + { + try b.realign(&mf, gpa, .@"8", true); + try a.realign(&mf, gpa, .@"16", true); + mf.verify(); + + try testVerifyContent(&mf, a, 0xaa, a_init_size); + try testVerifyContent(&mf, b, 0xbb, b_init_size); + try testVerifyContent(&mf, c, 0xcc, c_init_size); + try testVerifyContent(&mf, d, 0xdd, d_init_size); + } + + const child_init: []const struct { std.mem.Alignment, usize } = &.{ + .{ .@"8", 16 }, + .{ .@"1", 1 }, + .{ .@"1", 19 }, + .{ .@"1", 3 }, + .{ .@"4", 30 }, + .{ .@"2", 5 }, + .{ .@"16", 60 }, + .{ .@"2", 2 }, + .{ .@"16", 32 }, + }; + + var children: [child_init.len]Node.Index = undefined; + + // Differently-aligned fixed sibling nodes + { + for (children[0 .. children.len - 1], child_init[0 .. children.len - 1], 0..) |*ni, opts, i| { + ni.* = try mf.addLastChildNode(gpa, b, .{ + .alignment = opts.@"0", + .size = opts.@"1", + .fixed = true, + }); + + @memset(ni.slice(&mf)[0..opts.@"1"], @intCast(i + 1)); + } + // Shift differenntly-aligned nodes + children[children.len - 1] = try mf.addNodeAfter(gpa, children[3], .{ + .alignment = child_init[children.len - 1].@"0", + .size = child_init[children.len - 1].@"1", + .fixed = true, + }); + @memset(children[children.len - 1].slice(&mf), @intCast(children.len)); + + mf.verify(); + for (children, child_init, 0..) |ni, opts, i| { + try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1"); + } + } + + // Shifting child nodes forward due via resize of parent.prev + { + try testing.expect(a.location(&mf).resolve(&mf)[1] < 64); + try a.resize(&mf, gpa, 64); + + try testVerifyContent(&mf, a, 0xaa, a_init_size); + try testVerifyContent(&mf, c, 0xcc, c_init_size); + try testVerifyContent(&mf, d, 0xdd, d_init_size); + for (children, child_init, 0..) |ni, opts, i| { + try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1"); + } + } + + // Shrink and shift start of trailing node into free space + { + try mf.shrinkNode(gpa, a, 16, true); + mf.verify(); + + const a_loc, const a_size = a.location(&mf).resolve(&mf); + const b_loc, _ = b.location(&mf).resolve(&mf); + try testing.expectEqual(b_loc, a_loc + a_size); + + try testVerifyContent(&mf, a, 0xaa, a_init_size); + try testVerifyContent(&mf, c, 0xcc, c_init_size); + try testVerifyContent(&mf, d, 0xdd, d_init_size); + for (children, child_init, 0..) |ni, opts, i| { + try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1"); + } + } +} -- 2.54.0 From 5b7881cb2a1c83b8db7c6bae2ddaba8925a65a80 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 46/94] MappedFile: fix the trailing resize path in realignNode --- src/link/MappedFile.zig | 45 +++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 79624f1f0967c4acecb72b55a069af2ddb181f79..9eb3aa6ccb52ac5b3dac2d6a584c21af5a30c25c 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1362,7 +1362,7 @@ test { const a = try mf.addFirstChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" }); const c = try mf.addLastChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" }); - const b = try mf.addNodeAfter(gpa, a, .{ .fixed = true, .alignment = .@"4" }); + const b = try mf.addNodeAfter(gpa, a, .{ .fixed = true, .alignment = .@"16" }); const d = try mf.addNodeAfter(gpa, b, .{ .alignment = .@"4" }); const a_init_size = 8; @@ -1426,26 +1426,14 @@ test { try testVerifyContent(&mf, d, 0xdd, d_init_size); } - // Re-align nodes - { - try b.realign(&mf, gpa, .@"8", true); - try a.realign(&mf, gpa, .@"16", true); - mf.verify(); - - try testVerifyContent(&mf, a, 0xaa, a_init_size); - try testVerifyContent(&mf, b, 0xbb, b_init_size); - try testVerifyContent(&mf, c, 0xcc, c_init_size); - try testVerifyContent(&mf, d, 0xdd, d_init_size); - } - const child_init: []const struct { std.mem.Alignment, usize } = &.{ - .{ .@"8", 16 }, + .{ .@"16", 16 }, .{ .@"1", 1 }, .{ .@"1", 19 }, .{ .@"1", 3 }, - .{ .@"4", 30 }, + .{ .@"8", 30 }, .{ .@"2", 5 }, - .{ .@"16", 60 }, + .{ .@"1", 60 }, .{ .@"2", 2 }, .{ .@"16", 32 }, }; @@ -1463,7 +1451,7 @@ test { @memset(ni.slice(&mf)[0..opts.@"1"], @intCast(i + 1)); } - // Shift differenntly-aligned nodes + // Shift differently-aligned nodes by inserting a node children[children.len - 1] = try mf.addNodeAfter(gpa, children[3], .{ .alignment = child_init[children.len - 1].@"0", .size = child_init[children.len - 1].@"1", @@ -1490,6 +1478,29 @@ test { } } + // Re-align last node into trailing free space within parent + { + try b.resize(&mf, gpa, b.location(&mf).resolve(&mf)[1] + 64); + + const last = children[children.len - 2]; + try last.realign(&mf, gpa, .@"4", true); + mf.verify(); + + for (children, child_init, 0..) |ni, opts, i| + try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1"); + try testVerifyContent(&mf, c, 0xcc, c_init_size); + } + + // Re-align, shifting sibling nodes + { + try children[1].realign(&mf, gpa, .@"8", true); + mf.verify(); + + for (children, child_init, 0..) |ni, opts, i| + try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1"); + try testVerifyContent(&mf, c, 0xcc, c_init_size); + } + // Shrink and shift start of trailing node into free space { try mf.shrinkNode(gpa, a, 16, true); -- 2.54.0 From 6057145533c877ff5682e0dd08fc11b68ee8850c Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 47/94] Coff: put import thunks into their own section - Realign object sections if needed --- src/link/Coff.zig | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 5c48b4beceacc8142c8ad1a4168faeee719a791e..9c54f1c6d3278871857978073ae042f6a7ab0fb0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -805,6 +805,7 @@ pub const String = enum(u32) { @".bss" = 75, @".fptable" = 80, @".tls" = 89, + @".thunks" = 94, _, pub const Optional = enum(u32) { @@ -821,6 +822,7 @@ pub const String = enum(u32) { @".bss" = @intFromEnum(String.@".bss"), @".fptable" = @intFromEnum(String.@".fptable"), @".tls" = @intFromEnum(String.@".tls"), + @".thunks" = @intFromEnum(String.@".thunks"), none = std.math.maxInt(u32), _, @@ -3401,6 +3403,15 @@ fn objectSectionMapIndex( }; } + const old_alignment = sym.ni.alignment(&coff.mf); + if (alignment.compare(.gt, old_alignment)) { + log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment }); + sym.ni.realign(&coff.mf, gpa, alignment, true) catch |err| switch (err) { + error.Unimplemented => unreachable, + else => |e| return e, + }; + } + try coff.verifyParentSectionAttributes( .object, sym.section_number.name(coff), @@ -6182,9 +6193,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const lib_name = import.lib_name.toSlice(coff); try coff.nodes.ensureUnusedCapacity(gpa, 4); - try coff.symbols.ensureUnusedCapacity(gpa, 1); + try coff.symbols.ensureUnusedCapacity(gpa, 2); - const sym = si.get(coff); const target_endian = coff.targetEndian(); const addr_info = coff.targetAddrInfo(); const gop = try coff.import_table.entries.getOrPutAdapted( @@ -6328,6 +6338,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { } } + const sym = si.get(coff); assert(sym.loc_relocs == .none); const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*); switch (import.kind) { @@ -6340,21 +6351,31 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { (try gop.value_ptr.import_address_table_symbols.addOne(gpa)).* = si; }, .thunk => { - sym.section_number = Symbol.Index.text.get(coff).section_number; sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + + const target = &comp.root_mod.resolved_target.result; + const alignment = switch (comp.root_mod.optimize_mode) { + .Debug, + .ReleaseSafe, + .ReleaseFast, + => target_util.defaultFunctionAlignment(target), + .ReleaseSmall => target_util.minFunctionAlignment(target), + }.toStdMem(); + const parent_si = (try coff.pseudoSectionMapIndex( + .@".thunks", + alignment, + .{ .execute = true, .read = true }, + )).symbol(coff); + + const parent_sym = parent_si.get(coff); + sym.section_number = parent_sym.section_number; + switch (coff.targetLoad(&coff.headerPtr().machine)) { else => |tag| @panic(@tagName(tag)), .AMD64 => { const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 }; - const target = &comp.root_mod.resolved_target.result; - const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{ - .alignment = switch (comp.root_mod.optimize_mode) { - .Debug, - .ReleaseSafe, - .ReleaseFast, - => target_util.defaultFunctionAlignment(target), - .ReleaseSmall => target_util.minFunctionAlignment(target), - }.toStdMem(), + const ni = try coff.mf.addLastChildNode(gpa, parent_sym.ni, .{ + .alignment = alignment, .size = init.len, }); @memcpy(ni.slice(&coff.mf)[0..init.len], &init); -- 2.54.0 From bed106e5bf298adf8ce3f2c937f8fcaab14e21db Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 48/94] Coff: introduce resolve() - Move all idle() tasks that were modifying the node structure into resolve() - Add asserts to verify no node modification from idle() tasks - Fixup lib_name == "c" pulling in a non-existant c.dll, instead clear the lib_name and assume it comes from libc --- src/link/Coff.zig | 113 ++++++++++++++++++++++++++-------------- src/link/MappedFile.zig | 7 +++ 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 9c54f1c6d3278871857978073ae042f6a7ab0fb0..eb064f99c0ebc77293d9f7711800785fc15774a8 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -2663,11 +2663,28 @@ fn getOrPutGlobalSymbol( coff: *Coff, opts: GlobalOptions, ) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult { - const gpa = coff.base.comp.gpa; + const comp = coff.base.comp; + const gpa = comp.gpa; try coff.symbols.ensureUnusedCapacity(gpa, 1); + + const lib_name = if (opts.lib_name) |lib_name| lib_name: { + const is_libc = std.zig.target.isLibCLibName(&comp.root_mod.resolved_target.result, lib_name); + if (is_libc) { + // This is guaranteed by Sema.handleExternLibName + if (!comp.config.link_libc) unreachable; + + // TODO: The user has requested this symbol come from libc, but this logic allows + // it to come from anywhere. We need to know what inputs are libc inputs, + // and set a flag to only search them for this symbol. + break :lib_name null; + } + + break :lib_name lib_name; + } else null; + const sym_gop = try coff.globals.getOrPut(gpa, .{ .name = try coff.getOrPutString(opts.name), - .lib_name = try coff.getOrPutOptionalString(opts.lib_name), + .lib_name = try coff.getOrPutOptionalString(lib_name), }); if (!sym_gop.found_existing) { const si = coff.addSymbolAssumeCapacity(); @@ -5576,6 +5593,7 @@ pub fn flush( // this should be set after updateExports instead coff.exports_complete = true; + while (try coff.resolve(tid)) {} while (try coff.idle(tid)) {} if (coff.isImage()) @@ -5598,7 +5616,10 @@ pub fn flush( return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err}); } -pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { +/// Runs a single "resolution" task. +/// These are tasks that need to modify the node structure in some way. +/// They must run in a defined order with respect to linker tasks. +fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { const comp = coff.base.comp; task: { while (coff.section_merge_pending_index < coff.section_merges.count()) { @@ -5658,7 +5679,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; } - if (coff.inputs_complete and coff.global_pending_index < coff.globals.count()) { + if (coff.exports_complete and coff.global_pending_index < coff.globals.count()) { const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index); const sub_prog_node = coff.synth_prog_node.start( gmi.globalName(coff).name.toSlice(coff), @@ -5751,6 +5772,53 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; } + if (coff.symbol_table.pending_shrink) { + defer coff.symbol_table.pending_shrink = false; + const sub_prog_node = coff.idleProgNode( + tid, + coff.symbol_prog_node, + coff.getNode(coff.symbol_table.ni), + ); + defer sub_prog_node.end(); + + const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); + coff.symbol_table.ni.shrink( + &coff.mf, + comp.gpa, + number_of_symbols * std.coff.Symbol.sizeOf(), + true, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return comp.link_diags.fail( + "linker failed to compact symbol table: {t}", + .{e}, + ), + }; + + break :task; + } + } + + if (coff.section_merge_pending_index < coff.section_merges.count()) return true; + if (coff.pending_uavs.count() > 0) return true; + if (coff.pending_input != null) return true; + if (coff.exports_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.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.symbol_table.pending_shrink) return true; + return false; +} + +pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { + // Idle tasks should not modify create / modify nodes, otherwise the output is not reproducible. + coff.mf.nodes_lock.lock(); + defer coff.mf.nodes_lock.unlock(); + + const comp = coff.base.comp; + task: { // TODO: Idle task for flushing obj into lib if (coff.input_section_pending_index < coff.input_sections.items.len) { const isi: Node.InputSection.Index = @enumFromInt(coff.input_section_pending_index); @@ -5809,46 +5877,11 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { coff.flushExportsSort(); break :task; } - if (coff.symbol_table.pending_shrink) { - defer coff.symbol_table.pending_shrink = false; - const sub_prog_node = coff.idleProgNode( - tid, - coff.symbol_prog_node, - coff.getNode(coff.symbol_table.ni), - ); - defer sub_prog_node.end(); - - const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); - coff.symbol_table.ni.shrink( - &coff.mf, - comp.gpa, - number_of_symbols * std.coff.Symbol.sizeOf(), - true, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return comp.link_diags.fail( - "linker failed to compact symbol table: {t}", - .{e}, - ), - }; - - break :task; - } } - if (coff.section_merge_pending_index < coff.section_merges.count()) return true; - 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.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; if (coff.mf.updates.items.len > 0) return true; if (coff.pending_members.count() > 0) return true; if (coff.export_table.pending_sort) return true; - if (coff.symbol_table.pending_shrink) return true; return false; } @@ -6924,7 +6957,6 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void { }); var offset: u64 = 0; - var string_table = coff.secondLinkerMemberStringsSlice(); for (coff.lib_string_table.items) |string| { const str = string.toSlice(coff); @@ -7096,6 +7128,7 @@ fn updateExportsInner( Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu), ))), }; + while (try coff.resolve(pt.tid)) {} while (try coff.idle(pt.tid)) {} const machine = coff.targetLoad(&coff.headerPtr().machine); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 9eb3aa6ccb52ac5b3dac2d6a584c21af5a30c25c..a6edeb23a37e4e8a4d7fc4d2f4a82a7a7fcfcd40 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -26,6 +26,9 @@ updates: std.ArrayList(Node.Index), update_prog_node: std.Progress.Node, writers: std.SinglyLinkedList, io_err: ?IoError, +/// If locked, modifying the node layout is not allowed. +/// Modifying node content is always allowed. +nodes_lock: std.debug.SafetyLock = .{}, pub const growth_factor = 4; @@ -556,6 +559,7 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { add_node: AddNodeOptions, }) Error!Node.Index { if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1); + mf.nodes_lock.assertUnlocked(); const offset = opts.add_node.alignment.forward(@intCast(opts.offset)); if (opts.parent != .none) { const new_end = offset + opts.add_node.size; @@ -715,6 +719,7 @@ fn shrinkNode( size: u64, shift_next: bool, ) !void { + mf.nodes_lock.assertUnlocked(); const node = ni.get(mf); const old_offset, _ = node.location().resolve(mf); @@ -753,6 +758,7 @@ fn shrinkNode( } fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) (Allocator.Error || Io.Cancelable || IoError)!void { + mf.nodes_lock.assertUnlocked(); const io = mf.io; const node = ni.get(mf); const old_offset, const old_size = node.location().resolve(mf); @@ -1023,6 +1029,7 @@ fn realignNode( set_alignment: bool, ) (Allocator.Error || Io.Cancelable || IoError)!void { assert(ni != Node.Index.root); // currently unsupported + mf.nodes_lock.assertUnlocked(); const node = ni.get(mf); const old_offset, const size = node.location().resolve(mf); -- 2.54.0 From e91a1060512be07e4da889f4baadd1588f77ad94 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 49/94] Coff: fixup not applying thunk target relocs --- src/link/Coff.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index eb064f99c0ebc77293d9f7711800785fc15774a8..60929f6e0c7e5eef216edc988032f1e8b50b1799 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -6223,13 +6223,12 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return true; }; - const lib_name = import.lib_name.toSlice(coff); - try coff.nodes.ensureUnusedCapacity(gpa, 4); try coff.symbols.ensureUnusedCapacity(gpa, 2); const target_endian = coff.targetEndian(); const addr_info = coff.targetAddrInfo(); + const lib_name = import.lib_name.toSlice(coff); const gop = try coff.import_table.entries.getOrPutAdapted( gpa, lib_name, @@ -6380,7 +6379,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { sym.section_number = iat_sym.section_number; 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 => { @@ -6424,11 +6422,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }, } coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi }); - sym.rva = coff.computeNodeRva(sym.ni); - si.applyLocationRelocs(coff); }, } + si.flushMoved(coff); return true; } -- 2.54.0 From 5d8f8b571e245a9a4d72922d3939d2b1def0fb51 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 50/94] tests: update standalone/static_c_lib to test permutations of use_libc and use_llvm --- test/standalone/static_c_lib/build.zig | 87 ++++++++++++++++++++------ 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig index 4bed5cdefa5f496c702b60a051646d9e8979b177..a1e5c034fe10e4ebdd918264185e410964db9e73 100644 --- a/test/standalone/static_c_lib/build.zig +++ b/test/standalone/static_c_lib/build.zig @@ -5,26 +5,75 @@ pub fn build(b: *std.Build) void { b.default_step = test_step; const optimize: std.builtin.OptimizeMode = .Debug; + const target = b.standardTargetOptions(.{}); - const foo = b.addLibrary(.{ - .linkage = .static, - .name = "foo", - .root_module = b.createModule(.{ - .root_source_file = null, - .optimize = optimize, - .target = b.graph.host, - }), - }); - foo.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &[_][]const u8{} }); - foo.root_module.addIncludePath(b.path(".")); + const exe_names: []const []const u8 = &.{ + "test", + "test-dync", + "test-no-llvm", + "test-no-llvm-dync", + "test-exe-no-llvm", + "test-dync-exe-no-llvm", + "test-no-llvm-exe-no-llvm", + "test-no-llvm-dync-exe-no-llvm", + }; + const lib_names: []const []const u8 = &.{ + "foo", + "foo-dync", + "foo-no-llvm", + "foo-no-llvm-dync", + "foo-exe-no-llvm", + "foo-dync-exe-no-llvm", + "foo-no-llvm-exe-no-llvm", + "foo-no-llvm-dync-exe-no-llvm", + }; + const lib_link_libc: []const bool = &.{ false, true, false, true, false, true, false, true }; + const lib_use_llvm: []const bool = &.{ true, true, false, false, true, true, false, false }; + const exe_use_llvm: []const bool = &.{ true, true, true, true, false, false, false, false }; - const test_exe = b.addTest(.{ .root_module = b.createModule(.{ - .root_source_file = b.path("foo.zig"), - .target = b.graph.host, - .optimize = optimize, - }) }); - test_exe.root_module.linkLibrary(foo); - test_exe.root_module.addIncludePath(b.path(".")); + for ( + exe_names, + lib_names, + lib_link_libc, + lib_use_llvm, + exe_use_llvm, + ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| { + const use_llvm = lib_llvm or exe_llvm; + if (!use_llvm and target.result.os.tag == .macos) continue; // TODO + if (!use_llvm and target.result.os.tag == .freebsd) continue; // TODO + if (!use_llvm and target.result.os.tag == .netbsd) continue; // TODO + if (!use_llvm and target.result.os.tag == .openbsd) continue; // TODO + if (!use_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO + if (!use_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO + if (!use_llvm and target.result.cpu.arch == .s390x) continue; // TODO - test_step.dependOn(&b.addRunArtifact(test_exe).step); + const foo = b.addLibrary(.{ + .linkage = .static, + .name = lib_name, + .root_module = b.createModule(.{ + .root_source_file = null, + .optimize = optimize, + .target = target, + .link_libc = dyn_libc, + }), + .use_llvm = lib_llvm, + }); + foo.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &[_][]const u8{} }); + foo.root_module.addIncludePath(b.path(".")); + + const test_exe = b.addTest(.{ + .name = exe_name, + .root_module = b.createModule(.{ + .root_source_file = b.path("foo.zig"), + .target = target, + .optimize = optimize, + .link_libc = dyn_libc, + }), + .use_llvm = exe_llvm, + }); + test_exe.root_module.linkLibrary(foo); + test_exe.root_module.addIncludePath(b.path(".")); + + test_step.dependOn(&b.addRunArtifact(test_exe).step); + } } -- 2.54.0 From 601da46f154678e4a28f9c5496376deadb78d718 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 51/94] objdump: initial COFF implementation --- lib/compiler/objdump.zig | 561 ++++++++++++++++++++++++++++++++++++++- lib/std/coff.zig | 10 +- src/link/Coff.zig | 19 +- 3 files changed, 567 insertions(+), 23 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index d2af9c09c6e6e344794d0aaaa26f45efc2ce5d4c..1ee43a16f672a85ad6114c5f8eb5161d532e3296 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -4,19 +4,60 @@ const fatal = std.process.fatal; const mem = std.mem; const assert = std.debug.assert; +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + var stdout_buffer: [4000]u8 = undefined; +const Options = struct { + input_path: []const u8, + file_headers: bool, + section_filters: []const []const u8 = &.{}, + section_table: bool, + strings: bool, + symbols: bool, + compact: bool, +}; + pub fn main(init: std.process.Init) !void { const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); + const arena = init.arena.allocator(); - var opt_input_path: ?[]const u8 = null; var i: usize = 1; + + var opt_input_path: ?[]const u8 = null; + var opt_file_headers: ?bool = null; + var opt_section_table: ?bool = null; + var opt_strings: ?bool = null; + var opt_symbols: ?bool = null; + var opt_relocs: ?bool = null; + var opt_compact: ?bool = null; + var section_filters: std.ArrayList([]const u8) = .empty; while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { return Io.File.stdout().writeStreamingAll(io, usage); + } else if (mem.eql(u8, arg, "--all-headers")) { + opt_file_headers = true; + opt_section_table = true; + opt_symbols = true; + opt_relocs = true; + } else if (mem.eql(u8, arg, "--compact")) { + opt_compact = true; + } else if (mem.eql(u8, arg, "--file-headers")) { + opt_file_headers = true; + } else if (mem.startsWith(u8, arg, "--only-section=")) { + (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); + } else if (mem.eql(u8, arg, "--relocs")) { + opt_relocs = true; + } else if (mem.eql(u8, arg, "--section-headers")) { + opt_section_table = true; + } else if (mem.eql(u8, arg, "--strings")) { + opt_strings = true; + } else if (mem.eql(u8, arg, "--symbols")) { + opt_symbols = true; } else { fatal("unrecognized argument: {s}", .{arg}); } @@ -27,25 +68,35 @@ pub fn main(init: std.process.Init) !void { } } - const input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}); + const opts: Options = .{ + .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), + .compact = opt_compact orelse false, + .file_headers = opt_file_headers orelse false, + .section_filters = section_filters.items, + .section_table = opt_section_table orelse false, + .strings = opt_strings orelse false, + .symbols = opt_symbols orelse false, + }; - var file = std.Io.Dir.cwd().openFile(io, input_path, .{}) catch |err| - fatal("failed to open {s}: {t}", .{ input_path, err }); + var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err| + fatal("failed to open {s}: {t}", .{ opts.input_path, err }); defer file.close(io); - var buffer: [4000]u8 = undefined; + var buffer: [4096]u8 = undefined; var file_reader = file.reader(io, &buffer); var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); - dump(&file_reader.interface, &stdout_writer.interface) catch |err| switch (err) { + dump(arena, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) { error.ReadFailed => return file_reader.err.?, error.WriteFailed => return stdout_writer.err.?, - error.UnknownFile => fatal("unrecognized file: {s}", .{input_path}), + error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), + error.ParseFailure => {}, else => |e| return e, }; try stdout_writer.flush(); } -fn dump(r: *Io.Reader, w: *Io.Writer) !void { +fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { + const r = &fr.interface; try r.fill(4); elf: { if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf; @@ -60,9 +111,44 @@ fn dump(r: *Io.Reader, w: *Io.Writer) !void { if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm; return wasm.dump(r, w); } + coff: { + const ext = std.fs.path.extension(opts.input_path); + if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) { + if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff; + try r.discardAll(std.coff.pe_pointer_offset); + const sig_offset = try r.takeInt(u32, .little); + try fr.seekTo(sig_offset); + const sig = try r.take(4); + + if (!std.mem.eql(u8, sig, std.coff.pe_signature)) { + try w.print("invalid PE signature: {x}", .{sig}); + return error.ParseFailure; + } + + if (!opts.compact) try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)}); + return coff.dumpObject(arena, opts, true, fr, w); + } else if (std.mem.eql(u8, ext, ".lib")) { + r.fill(std.coff.archive_signature.len) catch break :coff; + if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; + if (!opts.compact) try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); + return coff.dumpArchive(opts, fr, w); + } else if (std.mem.eql(u8, ext, ".obj")) { + if (!opts.compact) try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); + return coff.dumpObject(arena, opts, false, fr, w); + } + } return error.UnknownFile; } +fn failParse( + opts: *const Options, + comptime fmt: []const u8, + args: anytype, +) noreturn { + std.log.err("error parsing '{s}'", .{std.fs.path.basename(opts.input_path)}); + fatal(fmt, args); +} + const elf = struct { fn dump(r: *Io.Reader, w: *Io.Writer) !void { _ = r; @@ -84,10 +170,467 @@ const wasm = struct { } }; +const coff = struct { + fn dumpArchive(opt: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { + _ = opt; + _ = fr; + try w.writeAll("TODO dump coff archive\n"); + } + + fn headerName(raw: *[8]u8, string_table: []const u8) ![]const u8 { + return if (raw[0] == '/') name: { + const name_offset = try std.fmt.parseUnsigned(u24, raw[1..], 10); + if (name_offset >= string_table.len) + return error.OutOfBounds; + + break :name std.mem.sliceTo(string_table[name_offset..], 0); + } else std.mem.sliceTo(raw, 0); + } + + fn dumpObject(arena: std.mem.Allocator, opts: *const Options, is_image: bool, fr: *Io.File.Reader, w: *Io.Writer) !void { + const r = &fr.interface; + const header = r.takeStruct(std.coff.Header, .little) catch |err| + return failParse(opts, "unable to read COFF header: {t}", .{err}); + + if (opts.file_headers) { + if (!opts.compact) try w.writeAll("COFF Header:\n"); + try dumpHeader(w, std.coff.Header, &header, struct {}); + if (!opts.compact) try w.writeByte('\n'); + } + + if (header.size_of_optional_header > 0) opt_header: { + if (!opts.file_headers) { + try fr.seekBy(header.size_of_optional_header); + break :opt_header; + } + + if (!opts.compact) try w.writeAll("COFF Optional Header:\n"); + const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); + const num_directory_entries = switch (magic) { + inline .PE32, .@"PE32+" => |v| data_dirs: { + const OptionalHeader = if (v == .PE32) + std.coff.OptionalHeader.PE32 + else + std.coff.OptionalHeader.@"PE32+"; + + const optional_header = r.takeStruct(OptionalHeader, .little) catch |err| + return failParse(opts, "unable to read optional header: {t}", .{err}); + + try dumpHeader(w, OptionalHeader, &optional_header, struct { + pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; + try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); + } + + pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; + try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); + } + + pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version); + } + pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {} + + pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField( + cw, + "operating_system_version", + h.major_operating_system_version, + h.minor_operating_system_version, + ); + } + pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + + pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version); + } + pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + + pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version); + } + pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + }); + if (!opts.compact) try w.writeByte('\n'); + + break :data_dirs optional_header.number_of_rva_and_sizes; + }, + else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), + }; + + if (!opts.compact) try w.writeAll("Data Directories:\n"); + for (0..num_directory_entries) |dir_i| { + const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| + return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); + + try w.print( + "{x: >16} {x: >8} {t}\n", + .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, + ); + } + if (!opts.compact) try w.writeByte('\n'); + } else if (is_image) { + return failParse(opts, "image did not contain an optional header", .{}); + } + + // Section names in images don't use the string table, as they must fit inline in the header + const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; + + const string_table = if (load_string_table) string_table: { + const pos = fr.logicalPos(); + fr.seekTo(header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| + return failParse(opts, "unable to seek to string table: {t}", .{err}); + + const string_table_len = r.peekInt(u32, .little) catch |err| + return failParse(opts, "unable to read string table length: {t}", .{err}); + + const table = r.readAlloc(arena, string_table_len) catch |err| + return failParse(opts, "unable to read string table: {t}", .{err}); + + try fr.seekTo(pos); + break :string_table table; + } else &.{}; + + var sections: std.ArrayList(std.coff.SectionHeader) = .empty; + const load_sections = opts.section_table or opts.symbols; + if (load_sections) { + if (!opts.compact and opts.section_table) + try w.writeAll( + \\Section Table: + \\Num Name RVA Virtual Size Data Size File Offset Relocs Offset Lines Offset # Relocs # Lines Flags + \\ + ); + + try sections.resize(arena, header.number_of_sections); + for (sections.items, 0..) |*section, section_i| { + section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err| + return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err }); + + if (opts.section_table) { + const name = headerName(§ion.name, string_table) catch |err| switch (err) { + error.Overflow, + error.InvalidCharacter, + => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{ + section.name, + err, + }), + error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{ + section.name, + string_table.len, + }), + }; + + const matched = for (opts.section_filters) |filter| { + if (std.mem.containsAtLeast(u8, name, 1, filter)) break true; + } else opts.section_filters.len == 0; + if (!matched) continue; + + try w.print( + "{x: >3} {s: <8} {x: >8} {x: >12} {x: >9} {x: >10} {x: >13} {x: >12} {x: >8} {x: >8} {x:0>8} ", + .{ + section_i + 1, + std.mem.sliceTo(§ion.name, 0), + section.virtual_address, + section.virtual_size, + section.size_of_raw_data, + section.pointer_to_raw_data, + section.pointer_to_relocations, + section.pointer_to_linenumbers, + section.number_of_relocations, + section.number_of_linenumbers, + @as(u32, @bitCast(section.flags)), + }, + ); + + if (name.len > 8) + try w.print(" | {s}", .{name}); + + try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, §ion.flags, 0); + try w.writeByte('\n'); + } + } + + if (!opts.compact and opts.section_table) try w.writeByte('\n'); + } + + if (opts.symbols) { + if (header.pointer_to_symbol_table > 0) { + fr.seekTo(header.pointer_to_symbol_table) catch |err| + return failParse(opts, "unable to seek to symbol table: {t}", .{err}); + + if (!opts.compact and opts.symbols) + try w.writeAll( + \\Symbol Table: + \\ Ord Value Sect Type Storage Name + \\ + ); + + const symbol_size = std.coff.Symbol.sizeOf(); + var symbol_i: u32 = 0; + while (symbol_i < header.number_of_symbols) { + var symbol: std.coff.Symbol = undefined; + const symbol_bytes = r.take(symbol_size) catch |err| + return failParse(opts, "unable to read symbol {x}: {t}", .{ symbol_i, err }); + + @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes); + if (native_endian != .little) + std.mem.byteSwapAllFields(std.coff.Symbol, &symbol); + + const aux_symbols = if (symbol.number_of_aux_symbols > 0) + try r.take(symbol_size * symbol.number_of_aux_symbols) + else + &.{}; + defer symbol_i += symbol.number_of_aux_symbols + 1; + + const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { + const index = std.mem.readInt(u32, symbol.name[4..], .little); + if (index >= string_table.len) + return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{ symbol_i, index, string_table.len }); + break :name string_table[index..]; + } else &symbol.name, 0); + + try w.print("{x:0>4} {x:0>8} ", .{ symbol_i, symbol.value }); + try switch (symbol.section_number) { + .UNDEFINED => w.writeAll("UNDEF"), + .ABSOLUTE => w.writeAll(" ABS"), + .DEBUG => w.writeAll("DEBUG"), + else => |v| { + const backing = @intFromEnum(v); + const fmt = "{x: >5}"; + if (backing >= 0) + try w.print(fmt, .{@as(u15, @intCast(backing))}) + else + try w.print(fmt, .{backing}); + }, + }; + + try w.print("{t: >5}", .{symbol.type.base_type}); + if (switch (symbol.type.complex_type) { + .NULL => " ", + .POINTER => "* ", + .FUNCTION => "()", + .ARRAY => "[]", + else => null, + }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type}); + + try w.print("{t: >16} | {s}", .{ symbol.storage_class, name }); + try w.writeByte('\n'); + + for (0..symbol.number_of_aux_symbols) |aux_i| { + _ = aux_i; + try w.writeAll(" AUX"); + + if (symbol.storage_class == .EXTERNAL and + symbol.type == std.coff.SymType{ + .complex_type = .FUNCTION, + .base_type = .NULL, + } and + @intFromEnum(symbol.section_number) > 0) + { + try w.writeAll("TODO function aux symbol"); + } else if (symbol.type == std.coff.SymType{ + .complex_type = .FUNCTION, + .base_type = .NULL, + } and + (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef"))) + { + try w.writeAll("TODO bf / ef aux symbol"); + } else if (symbol.storage_class == .EXTERNAL and + symbol.section_number == .UNDEFINED and + symbol.value == 0) + { + if (symbol.value != 0) + return failParse( + opts, + "invalid value 0x{x} for weak external symbol 0x{x}", + .{ symbol.value, symbol_i }, + ); + + var weak_external: std.coff.WeakExternalDefinition = undefined; + @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]); + if (native_endian != .little) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external); + + if (weak_external.tag_index >= header.number_of_symbols) + return failParse( + opts, + "invalid tag_index 0x{x} for weak external symbol 0x{x}", + .{ weak_external.tag_index, symbol_i }, + ); + + // TODO + + } else if (symbol.storage_class == .FILE) { + if (!std.mem.eql(u8, name, ".file")) { + try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i }); + continue; + } + + var file: std.coff.FileDefinition = undefined; + @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]); + + _ = file.getFileName(); + } else if (symbol.storage_class == .STATIC and + symbol.type == std.coff.SymType{ + .complex_type = .NULL, + .base_type = .NULL, + } and + symbol.value == 0 and + switch (symbol.section_number) { + .UNDEFINED, .DEBUG, .ABSOLUTE => false, + else => |sn| @intFromEnum(sn) > 0, + }) + { + const section_i: u15 = @intCast(@intFromEnum(symbol.section_number) - 1); + try w.writeAll(" Section "); + + if (section_i >= sections.items.len) { + try w.print(" !! invalid section number: {x}", .{section_i}); + continue; + } + + var section_def: std.coff.SectionDefinition = undefined; + @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], aux_symbols[0..symbol_size]); + if (native_endian != .little) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def); + + const section = §ions.items[section_i]; + if (section_def.number_of_relocations != section.number_of_relocations) { + try w.print( + " !! relocation count did not match section header: {d} vs {d}", + .{ section_def.number_of_relocations, section.number_of_relocations }, + ); + continue; + } + + if (section_def.number_of_linenumbers != section.number_of_linenumbers) { + try w.print( + " !! line number count did not match section header: {d} vs {d}", + .{ section_def.number_of_linenumbers, section.number_of_linenumbers }, + ); + continue; + } + + try w.print(" [size: {x:0>8} chksum: {x:0>8} relocs: {x:0>4} lines: {x:0>4}]", .{ + section_def.length, + section_def.checksum, + section_def.number_of_relocations, + section_def.number_of_linenumbers, + }); + + switch (section_def.selection) { + .NONE => {}, + else => |selection| { + try w.print(" COMDAT({t}", .{selection}); + if (selection == .ASSOCIATIVE) + try w.print("->{x}", .{section_def.number}); + try w.writeAll(")"); + }, + } + } else {} + + try w.writeByte('\n'); + } + } + } else { + if (!opts.compact) try w.writeAll("No symbol table found\n"); + } + } + } + + fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) { + return .{ .data = sym_type }; + } + + fn symbolTypeString(sym_type: std.coff.SymType, w: *std.Io.Writer) std.Io.Writer.Error!void { + try w.print("{t: >5}", .{sym_type.base_type}); + if (try switch (sym_type.complex_type) { + .NULL => " ", + .POINTER => "* ", + .FUNCTION => "()", + .ARRAY => "[]", + else => null, + }) |suffix| try .printAll(suffix) else w.print("{x}", .{sym_type.complex_type}); + } + + fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) { + return .{ .data = section_number }; + } + + fn sectionNumberString(section_number: std.coff.SectionNumber, w: *std.Io.Writer) std.Io.Writer.Error!void { + try switch (section_number) { + .UNDEFINED => w.writeAll("UNDEF"), + .ABSOLUTE => w.writeAll(" ABS"), + .DEBUG => w.writeAll("DEBUG"), + else => |v| { + const backing = @intFromEnum(v); + const fmt = "{x: >5}"; + if (backing >= 0) + try w.print(fmt, .{@as(u15, @intCast(backing))}) + else + try w.print(fmt, .{backing}); + }, + }; + } + + fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void { + const s = @typeInfo(T).@"struct"; + inline for (s.fields) |flag_field| { + if (flag_field.type == bool and @field(flags, flag_field.name)) { + try w.splatByteAll(' ', cols); + try w.print(fmt, .{flag_field.name}); + } + } + } + + fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void { + inline for (@typeInfo(T).@"struct".fields) |field| { + const val = &@field(header, field.name); + if (@hasDecl(Custom, field.name)) { + try @field(Custom, field.name)(header, w); + } else { + switch (@typeInfo(field.type)) { + .int => try w.print("{x: >16} {s}\n", .{ val.*, field.name }), + .@"enum" => try w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }), + .@"struct" => |s| { + switch (s.layout) { + .auto, + .@"extern", + => try dumpHeader(w, field.type, val, Custom), + .@"packed" => { + try w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name }); + try dumpFlags(w, "| {s}\n", field.type, val, 15); + }, + } + }, + else => unreachable, + } + } + } + } + + fn dumpVersionField(w: *Io.Writer, name: []const u8, major: anytype, minor: anytype) !void { + try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name }); + } + + fn dumpRvaField(w: *Io.Writer, name: []const u8, rva: u64, base: u64) !void { + try w.print("{x: >16} {s} ({x})\n", .{ rva, name, base + rva }); + } +}; + const usage = \\Usage: zig objdump [options] file \\ \\Options: \\ -h, --help Print this help and exit - \\ + \\ --all-headers Alias for --file-headers --section-headers --relocs --symbols + \\ --compact Minimal output mode that excludes extra newlines and headings. Intended for snapshot testing. + \\ --file-headers Display file-format specific headers + \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. + \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. + \\ --section-headers Display section headers + \\ --strings Display string table + \\ --symbols Display symbol tables + \\ --relocs Display relocations ; diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 4f5ccf7f79b78dd5a9c0c8cc3613a0ac07cebf8c..943bdc83c97455cb7943c8d447a26b86b817ecc3 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -2,6 +2,11 @@ const std = @import("std.zig"); const assert = std.debug.assert; const mem = std.mem; +pub const archive_signature = "!\n"; + +pub const pe_signature = "PE\x00\x00"; +pub const pe_pointer_offset = 0x3C; + pub const Header = extern struct { /// The number that identifies the type of target machine. machine: IMAGE.FILE.MACHINE, @@ -1019,13 +1024,10 @@ pub const Coff = struct { // The lifetime of `data` must be longer than the lifetime of the returned Coff pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff { - const pe_pointer_offset = 0x3C; - const pe_magic = "PE\x00\x00"; - if (data.len < pe_pointer_offset + 4) return error.EndOfStream; const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little); if (data.len < header_offset + 4) return error.EndOfStream; - const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic); + const is_image = mem.eql(u8, data[header_offset..][0..4], pe_signature); const coff: Coff = .{ .data = data, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 60929f6e0c7e5eef216edc988032f1e8b50b1799..f159148a20cce20544f1ab09f6433a17c9bfd899 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -90,7 +90,6 @@ pub const default_size_of_stack_commit: u32 = 0x1000; pub const default_size_of_heap_reserve: u32 = 0x100000; pub const default_size_of_heap_commit: u32 = 0x1000; -pub const archive_signature = "!\n"; pub const archive_end_of_header = "`\n"; pub const imp_prefix = "__imp_"; @@ -1763,14 +1762,12 @@ fn initHeaders( })); coff.nodes.appendAssumeCapacity(.header); - const pe_signature = "PE\x00\x00"; - const signature_ni = Node.known.signature; assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{ .size = if (is_image) - msdos_stub.len + pe_signature.len + msdos_stub.len + std.coff.pe_signature.len else if (is_archive) - archive_signature.len + std.coff.archive_signature.len else 0, .alignment = .@"4", @@ -1781,9 +1778,9 @@ fn initHeaders( const signature_slice = signature_ni.slice(&coff.mf); if (is_image) { @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub); - @memcpy(signature_slice[signature_slice.len - pe_signature.len ..], pe_signature); + @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature); } else if (is_archive) { - @memcpy(signature_slice, archive_signature); + @memcpy(signature_slice, std.coff.archive_signature); } const opt_coff_parent_ni = if (is_archive) parent: { @@ -3679,7 +3676,7 @@ fn loadObject( log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) }); - const header = try r.peekStruct(std.coff.Header, coff.targetEndian()); + const header = try r.peekStruct(std.coff.Header, .little()); if (header.machine != target.toCoffMachine()) return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{ target.toCoffMachine(), @@ -4786,8 +4783,8 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo log.debug("loadArchive({f})", .{path.fmtEscapeString()}); - const signature = try r.take(archive_signature.len); - if (!std.mem.eql(u8, signature, archive_signature)) + const signature = try r.take(std.coff.archive_signature.len); + if (!std.mem.eql(u8, signature, std.coff.archive_signature)) return diags.failParse(path, "bad signature", .{}); var opt_expected_kind: ?Member.Kind = .first_linker; @@ -6375,6 +6372,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*); switch (import.kind) { .iat_ptr => { + // TODO: Currently the codegen is wrong for loading the address of these globals, + // we generate lea [] when it should be mov [] const iat_sym = gop.value_ptr.import_address_table_si.get(coff); sym.section_number = iat_sym.section_number; sym.ni = iat_sym.ni; -- 2.54.0 From 224d7e7999fbe81c17abf7a154260b1a63675f63 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 52/94] objdump: more coff progress --- lib/compiler/objdump.zig | 416 ++++++++++++++++++++++++++++++++------- lib/std/coff.zig | 68 +++++++ src/link/Coff.zig | 51 ++--- 3 files changed, 426 insertions(+), 109 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 1ee43a16f672a85ad6114c5f8eb5161d532e3296..ef426ce9fa8620e85ef52c1fbaf2a55943fb6208 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -12,11 +12,15 @@ var stdout_buffer: [4000]u8 = undefined; const Options = struct { input_path: []const u8, file_headers: bool, + member_filters: []const []const u8 = &.{}, + member_headers: bool, section_filters: []const []const u8 = &.{}, - section_table: bool, + section_headers: bool, strings: bool, symbols: bool, - compact: bool, + + // Coff-specific + linker_member: ?std.coff.ArchiveMemberHeader.Kind, }; pub fn main(init: std.process.Init) !void { @@ -28,12 +32,14 @@ pub fn main(init: std.process.Init) !void { var opt_input_path: ?[]const u8 = null; var opt_file_headers: ?bool = null; - var opt_section_table: ?bool = null; + var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null; + var opt_member_headers: ?bool = null; + var opt_section_headers: ?bool = null; var opt_strings: ?bool = null; var opt_symbols: ?bool = null; var opt_relocs: ?bool = null; - var opt_compact: ?bool = null; var section_filters: std.ArrayList([]const u8) = .empty; + var member_filters: std.ArrayList([]const u8) = .empty; while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { @@ -41,19 +47,27 @@ pub fn main(init: std.process.Init) !void { return Io.File.stdout().writeStreamingAll(io, usage); } else if (mem.eql(u8, arg, "--all-headers")) { opt_file_headers = true; - opt_section_table = true; + opt_member_headers = true; + opt_section_headers = true; opt_symbols = true; opt_relocs = true; - } else if (mem.eql(u8, arg, "--compact")) { - opt_compact = true; } else if (mem.eql(u8, arg, "--file-headers")) { opt_file_headers = true; + } else if (mem.startsWith(u8, arg, "--linker-member")) { + if (mem.eql(u8, arg["--linker-member".len..], "=1")) + opt_linker_member = .first_linker + else + opt_linker_member = .second_linker; + } else if (mem.eql(u8, arg, "--member-headers")) { + opt_member_headers = true; } else if (mem.startsWith(u8, arg, "--only-section=")) { (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); + } else if (mem.startsWith(u8, arg, "--only-member=")) { + (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]); } else if (mem.eql(u8, arg, "--relocs")) { opt_relocs = true; } else if (mem.eql(u8, arg, "--section-headers")) { - opt_section_table = true; + opt_section_headers = true; } else if (mem.eql(u8, arg, "--strings")) { opt_strings = true; } else if (mem.eql(u8, arg, "--symbols")) { @@ -70,12 +84,14 @@ pub fn main(init: std.process.Init) !void { const opts: Options = .{ .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), - .compact = opt_compact orelse false, .file_headers = opt_file_headers orelse false, .section_filters = section_filters.items, - .section_table = opt_section_table orelse false, + .section_headers = opt_section_headers orelse false, .strings = opt_strings orelse false, .symbols = opt_symbols orelse false, + .member_filters = member_filters.items, + .member_headers = opt_member_headers orelse false, + .linker_member = opt_linker_member, }; var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err| @@ -85,7 +101,7 @@ pub fn main(init: std.process.Init) !void { var buffer: [4096]u8 = undefined; var file_reader = file.reader(io, &buffer); var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); - dump(arena, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) { + dump(init.gpa, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) { error.ReadFailed => return file_reader.err.?, error.WriteFailed => return stdout_writer.err.?, error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), @@ -95,7 +111,7 @@ pub fn main(init: std.process.Init) !void { try stdout_writer.flush(); } -fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { +fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { const r = &fr.interface; try r.fill(4); elf: { @@ -125,16 +141,16 @@ fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: return error.ParseFailure; } - if (!opts.compact) try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)}); - return coff.dumpObject(arena, opts, true, fr, w); + try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)}); + return coff.dumpObject(gpa, opts, true, fr, w); } else if (std.mem.eql(u8, ext, ".lib")) { r.fill(std.coff.archive_signature.len) catch break :coff; if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; - if (!opts.compact) try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); - return coff.dumpArchive(opts, fr, w); + try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); + return coff.dumpArchive(gpa, opts, fr, w); } else if (std.mem.eql(u8, ext, ".obj")) { - if (!opts.compact) try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); - return coff.dumpObject(arena, opts, false, fr, w); + try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); + return coff.dumpObject(gpa, opts, false, fr, w); } } return error.UnknownFile; @@ -171,31 +187,249 @@ const wasm = struct { }; const coff = struct { - fn dumpArchive(opt: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { - _ = opt; - _ = fr; - try w.writeAll("TODO dump coff archive\n"); - } + const ArchiveHeader = struct { + name: []const u8, + date: u40, + user_id: u20, + group_id: u20, + file_mode: u24, + size: u34, + + pub fn fromRaw(opts: *const Options, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() { + const name = raw_header.parseName(opt_longnames) catch |err| switch (err) { + error.BadName => failParse(opts, "malformed member name: '{s}'", .{&raw_header.name}), + error.NoLongNames => failParse(opts, "member uses a long name, but there was no longnames member", .{}), + }; + + return .{ + .name = name, + .date = raw_header.parseDate() catch |err| + failParse(opts, "unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }), + .user_id = raw_header.parseUserId() catch |err| + failParse(opts, "unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }), + .group_id = raw_header.parseGroupId() catch |err| + failParse(opts, "unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }), + .file_mode = raw_header.parseFileMode() catch |err| + failParse(opts, "unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }), + .size = raw_header.parseSize() catch |err| + failParse(opts, "unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }), + }; + } + }; + + fn dumpArchive(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { + const r = &fr.interface; + r.toss(std.coff.archive_signature.len); + + var members: std.ArrayList(struct { + offset: u32, + }) = .empty; + defer members.deinit(gpa); + var symbol_member_indices: std.ArrayList(u32) = .empty; + defer symbol_member_indices.deinit(gpa); + + var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker; + var opt_longnames: ?[]const u8 = null; + defer if (opt_longnames) |l| gpa.free(l); + + var pos = fr.logicalPos(); + const size = try fr.getSize(); + while (pos < size) : (pos = fr.logicalPos()) { + if ((pos & 1) != 0) try r.discardAll(1); + const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); + const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames); + + if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header)) + return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header }); + + const dump_header = opts.member_headers and filterMatches(opts.member_filters, header.name); + if (dump_header) + try dumpArchiveHeader(w, &header, @intCast(pos)); + + const member_end = fr.logicalPos() + header.size; + if (member_end > size) + return failParse(opts, "out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name }); + + if (opt_expected_kind) |expected_kind| switch (expected_kind) { + .first_linker => { + if (!std.mem.eql(u8, header.name, "/")) + return failParse(opts, "expected first linker member, found '{s}'", .{header.name}); + + const num_symbols = try r.takeInt(u32, .big); + if (dump_header) + try w.print( + \\{t: >16} type + \\ | {d} symbols + \\ + , .{ expected_kind, num_symbols }); + + if (opts.linker_member == .first_linker) { + try w.print( + \\Archives symbols ({d}): + \\& Member Symbol + \\ + , .{num_symbols}); + + const offsets = try r.readAlloc(gpa, num_symbols * 4); + defer gpa.free(offsets); + + for (0..num_symbols) |symbol_i| { + const symbol = r.takeDelimiter(0) catch |err| + return failParse(opts, "unable to read first linker member string table: {t}", .{err}); + try w.print("{x: >8} {s}\n", .{ std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big), symbol.? }); + } + } + if (dump_header) try w.writeByte('\n'); + + try fr.seekTo(member_end); + opt_expected_kind = .second_linker; + continue; + }, + .second_linker => { + if (!std.mem.eql(u8, header.name, "/")) + return failParse(opts, "expected second linker member, found '{s}'", .{header.name}); + + // TODO: Figure out what endianness is actually used, there are no headers to say yet? + + const num_members = try r.takeInt(u32, .little); + pos = fr.logicalPos(); + if (pos + num_members * @sizeOf(u32) > member_end) + return failParse(opts, "invalid member count 0x{x} in second linker member", .{num_members}); + + try members.ensureTotalCapacity(gpa, num_members); + for (0..num_members) |_| + members.addOneAssumeCapacity().* = .{ + .offset = try r.takeInt(u32, .little), + }; + + const num_symbols = try r.takeInt(u32, .little); + pos = fr.logicalPos(); + if (pos + num_symbols * @sizeOf(u16) > member_end) + return failParse(opts, "invalid symbol count 0x{x} in second linker member", .{num_symbols}); + + if (dump_header) + try w.print( + \\{t: >16} type + \\ | {d} symbols + \\ | {d} members + \\ + , .{ expected_kind, num_symbols, num_members }); + + try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); + for (0..num_symbols) |_| + symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1; + + if (opts.linker_member == .second_linker) { + try w.print( + \\Archive symbols ({d} members, {d} symbols): + \\& Member Symbol + \\ + , .{ num_members, num_symbols }); + + pos = fr.logicalPos(); + var symbol_i: u32 = 0; + while (pos < member_end and symbol_i < num_symbols) : ({ + pos = fr.logicalPos(); + symbol_i += 1; + }) { + const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) { + error.StreamTooLong => null, + else => |e| return e, + }) |n| n else return failParse(opts, "unterminated string found in second linker member", .{}); + + try w.print("{x: >8} {s}\n", .{ + members.items[symbol_member_indices.items[symbol_i]].offset, + symbol_name, + }); + } + + if (symbol_i != num_symbols) + return failParse( + opts, + " expected {d} entries in second linker member string table, but found {d}", + .{ num_symbols, symbol_i }, + ); + } + + try w.writeByte('\n'); + try fr.seekTo(member_end); + opt_expected_kind = .longnames; + continue; + }, + .longnames => { + // This member is optional + if (std.mem.eql(u8, header.name, "//")) { + opt_longnames = try r.readAlloc(gpa, header.size); + if (dump_header) + try w.print("{t: >16} type\n", .{expected_kind}); + } + + opt_expected_kind = null; + break; + }, + else => unreachable, + }; + } + + if (opt_expected_kind) |expected_kind| switch (expected_kind) { + .first_linker => failParse(opts, "missing first linker member", .{}), + .second_linker => failParse(opts, "missing second linker member", .{}), + else => {}, + }; + + for (members.items, 0..) |member, member_i| { + fr.seekTo(member.offset) catch |err| + failParse(opts, "unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err }); + + const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); + const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames); + if (!filterMatches(opts.member_filters, header.name)) continue; + + const member_sig = try r.peek(4); + const machine: std.coff.IMAGE.FILE.MACHINE = + @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little)); + const sig = std.mem.readInt(u16, member_sig[2..4], .little); + + const is_import = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; - fn headerName(raw: *[8]u8, string_table: []const u8) ![]const u8 { - return if (raw[0] == '/') name: { - const name_offset = try std.fmt.parseUnsigned(u24, raw[1..], 10); - if (name_offset >= string_table.len) - return error.OutOfBounds; + if (opts.member_headers) { + try dumpArchiveHeader(w, &header, member.offset); + if (is_import) { + try w.writeAll(" Import header type\n"); + // TODO: Dump import header + } else { + try w.writeAll(" COFF object type\n"); + } + try w.writeByte('\n'); + } - break :name std.mem.sliceTo(string_table[name_offset..], 0); - } else std.mem.sliceTo(raw, 0); + if (opts.section_headers or + opts.file_headers or + opts.strings or + opts.symbols) + { + try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(opts.input_path), header.name }); + try dumpObject(gpa, opts, false, fr, w); + } + } } - fn dumpObject(arena: std.mem.Allocator, opts: *const Options, is_image: bool, fr: *Io.File.Reader, w: *Io.Writer) !void { + fn dumpObject( + gpa: std.mem.Allocator, + opts: *const Options, + is_image: bool, + fr: *Io.File.Reader, + w: *Io.Writer, + ) !void { + const file_location = fr.logicalPos(); const r = &fr.interface; const header = r.takeStruct(std.coff.Header, .little) catch |err| return failParse(opts, "unable to read COFF header: {t}", .{err}); if (opts.file_headers) { - if (!opts.compact) try w.writeAll("COFF Header:\n"); + try w.writeAll("COFF Header:\n"); try dumpHeader(w, std.coff.Header, &header, struct {}); - if (!opts.compact) try w.writeByte('\n'); + try w.writeByte('\n'); } if (header.size_of_optional_header > 0) opt_header: { @@ -204,7 +438,7 @@ const coff = struct { break :opt_header; } - if (!opts.compact) try w.writeAll("COFF Optional Header:\n"); + try w.writeAll("COFF Optional Header:\n"); const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); const num_directory_entries = switch (magic) { inline .PE32, .@"PE32+" => |v| data_dirs: { @@ -252,14 +486,14 @@ const coff = struct { } pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} }); - if (!opts.compact) try w.writeByte('\n'); + try w.writeByte('\n'); break :data_dirs optional_header.number_of_rva_and_sizes; }, else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), }; - if (!opts.compact) try w.writeAll("Data Directories:\n"); + try w.writeAll("Data Directories:\n"); for (0..num_directory_entries) |dir_i| { const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); @@ -269,45 +503,62 @@ const coff = struct { .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, ); } - if (!opts.compact) try w.writeByte('\n'); + try w.writeByte('\n'); } else if (is_image) { return failParse(opts, "image did not contain an optional header", .{}); } // Section names in images don't use the string table, as they must fit inline in the header const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; - const string_table = if (load_string_table) string_table: { const pos = fr.logicalPos(); - fr.seekTo(header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| + fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| return failParse(opts, "unable to seek to string table: {t}", .{err}); const string_table_len = r.peekInt(u32, .little) catch |err| return failParse(opts, "unable to read string table length: {t}", .{err}); - const table = r.readAlloc(arena, string_table_len) catch |err| + const table = r.readAlloc(gpa, string_table_len) catch |err| return failParse(opts, "unable to read string table: {t}", .{err}); try fr.seekTo(pos); break :string_table table; } else &.{}; + defer gpa.free(string_table); + + if (opts.strings) { + try w.print( + \\String Table (0x{x} bytes): + \\ + , .{string_table.len}); + + var sr = Io.Reader.fixed(string_table[4..]); + while (try sr.takeDelimiter(0)) |str| { + try w.writeAll(str); + try w.writeByte('\n'); + } + + try w.writeByte('\n'); + } var sections: std.ArrayList(std.coff.SectionHeader) = .empty; - const load_sections = opts.section_table or opts.symbols; + defer sections.deinit(gpa); + + const load_sections = opts.section_headers or opts.symbols; if (load_sections) { - if (!opts.compact and opts.section_table) + if (opts.section_headers) try w.writeAll( \\Section Table: - \\Num Name RVA Virtual Size Data Size File Offset Relocs Offset Lines Offset # Relocs # Lines Flags + \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags \\ ); - try sections.resize(arena, header.number_of_sections); + try sections.resize(gpa, header.number_of_sections); for (sections.items, 0..) |*section, section_i| { section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err| return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err }); - if (opts.section_table) { + if (opts.section_headers) { const name = headerName(§ion.name, string_table) catch |err| switch (err) { error.Overflow, error.InvalidCharacter, @@ -321,16 +572,13 @@ const coff = struct { }), }; - const matched = for (opts.section_filters) |filter| { - if (std.mem.containsAtLeast(u8, name, 1, filter)) break true; - } else opts.section_filters.len == 0; - if (!matched) continue; - + if (!filterMatches(opts.section_filters, name)) continue; + const raw_name = std.mem.sliceTo(§ion.name, 0); try w.print( - "{x: >3} {s: <8} {x: >8} {x: >12} {x: >9} {x: >10} {x: >13} {x: >12} {x: >8} {x: >8} {x:0>8} ", + "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ", .{ section_i + 1, - std.mem.sliceTo(§ion.name, 0), + raw_name, section.virtual_address, section.virtual_size, section.size_of_raw_data, @@ -343,28 +591,27 @@ const coff = struct { }, ); - if (name.len > 8) - try w.print(" | {s}", .{name}); - try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, §ion.flags, 0); + if (name.len > 8) + try w.print("| {s}", .{name}); + try w.writeByte('\n'); } } - if (!opts.compact and opts.section_table) try w.writeByte('\n'); + if (opts.section_headers) try w.writeByte('\n'); } if (opts.symbols) { if (header.pointer_to_symbol_table > 0) { - fr.seekTo(header.pointer_to_symbol_table) catch |err| + fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err| return failParse(opts, "unable to seek to symbol table: {t}", .{err}); - if (!opts.compact and opts.symbols) - try w.writeAll( - \\Symbol Table: - \\ Ord Value Sect Type Storage Name - \\ - ); + try w.writeAll( + \\Symbol Table: + \\ Ord Value Sect Type Storage Name + \\ + ); const symbol_size = std.coff.Symbol.sizeOf(); var symbol_i: u32 = 0; @@ -390,7 +637,7 @@ const coff = struct { break :name string_table[index..]; } else &symbol.name, 0); - try w.print("{x:0>4} {x:0>8} ", .{ symbol_i, symbol.value }); + try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value }); try switch (symbol.section_number) { .UNDEFINED => w.writeAll("UNDEF"), .ABSOLUTE => w.writeAll(" ABS"), @@ -419,7 +666,7 @@ const coff = struct { for (0..symbol.number_of_aux_symbols) |aux_i| { _ = aux_i; - try w.writeAll(" AUX"); + try w.writeAll(" |"); if (symbol.storage_class == .EXTERNAL and symbol.type == std.coff.SymType{ @@ -512,7 +759,7 @@ const coff = struct { continue; } - try w.print(" [size: {x:0>8} chksum: {x:0>8} relocs: {x:0>4} lines: {x:0>4}]", .{ + try w.print(" [size {x:0>8} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{ section_def.length, section_def.checksum, section_def.number_of_relocations, @@ -533,12 +780,24 @@ const coff = struct { try w.writeByte('\n'); } } + + try w.writeByte('\n'); } else { - if (!opts.compact) try w.writeAll("No symbol table found\n"); + try w.writeAll("No symbol table found\n"); } } } + fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 { + return if (raw[0] == '/') name: { + const name_offset = try std.fmt.parseUnsigned(u24, std.mem.sliceTo(raw[1..], 0), 10); + if (name_offset >= string_table.len) + return error.OutOfBounds; + + break :name std.mem.sliceTo(string_table[name_offset..], 0); + } else std.mem.sliceTo(raw, 0); + } + fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) { return .{ .data = sym_type }; } @@ -584,6 +843,18 @@ const coff = struct { } } + fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void { + try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); + + // TODO: Date formatter + try dumpHeader(w, ArchiveHeader, header, struct { + pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {} + pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void { + try cw.print("{o: >16} file_mode\n", .{h.file_mode}); + } + }); + } + fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void { inline for (@typeInfo(T).@"struct".fields) |field| { const val = &@field(header, field.name); @@ -619,14 +890,21 @@ const coff = struct { } }; +fn filterMatches(filters: []const []const u8, val: []const u8) bool { + return for (filters) |filter| { + if (std.mem.containsAtLeast(u8, val, 1, filter)) break true; + } else filters.len == 0; +} + const usage = \\Usage: zig objdump [options] file \\ \\Options: \\ -h, --help Print this help and exit - \\ --all-headers Alias for --file-headers --section-headers --relocs --symbols - \\ --compact Minimal output mode that excludes extra newlines and headings. Intended for snapshot testing. + \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols \\ --file-headers Display file-format specific headers + \\ --linker-member[=1|2] (Coff) Display contents of the specified linker member (default 2) + \\ --member-headers Display archive member headers \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. \\ --section-headers Display section headers diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 943bdc83c97455cb7943c8d447a26b86b817ecc3..9d7f1c261822ea00adbc222067980e3b30afbbc0 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -3,6 +3,7 @@ const assert = std.debug.assert; const mem = std.mem; pub const archive_signature = "!\n"; +pub const archive_end_of_header = "`\n"; pub const pe_signature = "PE\x00\x00"; pub const pe_pointer_offset = 0x3C; @@ -1990,6 +1991,73 @@ pub const ArchiveMemberHeader = extern struct { size: [10]u8, /// The literal string '`\n' end_of_header: [2]u8, + + /// Extracts the name of the member by either reading it directly from + /// the header, or by finding it inside the longnames member, if provided. + pub fn parseName( + self: *const ArchiveMemberHeader, + opt_longnames: ?[]const u8, + ) ![]const u8 { + const trim = std.mem.trimEnd(u8, &self.name, &.{' '}); + + if (trim.len == 0) return error.BadName; + return if (trim[0] == '/') name: { + if (trim.len == 1 or + trim.len == 2 and trim[1] == '/') + break :name trim; + + const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch + return error.BadName; + + if (opt_longnames) |longnames| { + if (offset >= longnames.len) return error.BadName; + break :name std.mem.sliceTo(longnames[offset..], 0); + } else return error.NoLongNames; + } else if (trim[trim.len - 1] == '/') + trim[0 .. trim.len - 1] + else + return error.BadName; + } + + fn parseField(field: []const u8, T: type, base: u8) !T { + if (std.mem.allEqual(u8, field, ' ')) return 0; + if (field[0] == '-') + return @bitCast(try std.fmt.parseInt( + @Int(.signed, @typeInfo(T).int.bits), + std.mem.trimEnd(u8, field, &.{' '}), + base, + )); + + return std.fmt.parseUnsigned(T, std.mem.trimEnd(u8, field, &.{' '}), base); + } + + pub fn parseDate(self: *const ArchiveMemberHeader) !u40 { + return parseField(&self.date, u40, 10); + } + + pub fn parseUserId(self: *const ArchiveMemberHeader) !u20 { + return parseField(&self.user_id, u20, 10); + } + + pub fn parseGroupId(self: *const ArchiveMemberHeader) !u20 { + return parseField(&self.group_id, u20, 10); + } + + pub fn parseFileMode(self: *const ArchiveMemberHeader) !u20 { + return parseField(&self.group_id, u20, 8); + } + + pub fn parseSize(self: *const ArchiveMemberHeader) !u34 { + return parseField(&self.size, u34, 10); + } + + pub const Kind = enum { + first_linker, + second_linker, + longnames, + coff, + import, + }; }; pub const LineNumber = extern struct { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index f159148a20cce20544f1ab09f6433a17c9bfd899..20221e09dfa23550543f025862839b7067133e3b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -90,8 +90,6 @@ pub const default_size_of_stack_commit: u32 = 0x1000; pub const default_size_of_heap_reserve: u32 = 0x100000; pub const default_size_of_heap_commit: u32 = 0x1000; -pub const archive_end_of_header = "`\n"; - pub const imp_prefix = "__imp_"; const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len; @@ -472,7 +470,7 @@ pub const InputObject = struct { }; pub const Member = struct { - kind: Kind, + kind: std.coff.ArchiveMemberHeader.Kind, header_ni: MappedFile.Node.Index, content_ni: MappedFile.Node.Index, first_linker_indices: std.AutoArrayHashMapUnmanaged(struct { @@ -480,14 +478,6 @@ pub const Member = struct { name: String, }, FirstLinkerIndex), - pub const Kind = enum { - first_linker, - second_linker, - longnames, - coff, - import, - }; - pub const Index = enum(u16) { first, second, @@ -569,7 +559,7 @@ pub const Member = struct { member.content_ni.location(&coff.mf).resolve(&coff.mf)[1], ); - @memcpy(&header.end_of_header, archive_end_of_header); + @memcpy(&header.end_of_header, std.coff.archive_end_of_header); } pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void { @@ -2832,7 +2822,7 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. } /// Caller guarantees there is capacity for one member and two nodes -fn addMemberAssumeCapacity(coff: *Coff, kind: Member.Kind, size: usize) !Member.Index { +fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: usize) !Member.Index { const comp = coff.base.comp; const gpa = comp.gpa; @@ -4731,41 +4721,22 @@ fn parseArchiveMemberHeader( opt_longnames: ?[]const u8, ) !ArchiveMemberHeader { return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) { - error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}), - error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}), - error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}), + error.BadName => return diags.failParse(path, "malformed member name: '{s}'", .{&header.name}), + error.BadSize => return diags.failParse(path, "malformed member size: '{s}'", .{&header.size}), + error.BadEndOfHeader => return diags.failParse(path, "end of header was invalid", .{}), error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}), }; } +// TODO: Move to std.coff? fn parseArchiveMemberHeaderInner( header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8, ) !ArchiveMemberHeader { - const trim = std.mem.trimEnd(u8, &header.name, &.{' '}); + const name = try header.parseName(opt_longnames); + const size = header.parseSize() catch return error.BadSize; - if (trim.len == 0) return error.BadName; - const name = if (trim[0] == '/') name: { - if (trim.len == 1 or - trim.len == 2 and trim[1] == '/') - break :name trim; - - const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch - return error.BadName; - - if (opt_longnames) |longnames| { - if (offset >= longnames.len) return error.BadName; - break :name std.mem.sliceTo(longnames[offset..], 0); - } else return error.NoLongNames; - } else if (trim[trim.len - 1] == '/') - trim[0 .. trim.len - 1] - else - return error.BadName; - - const size = std.fmt.parseUnsigned(u34, std.mem.trimEnd(u8, &header.size, &.{' '}), 10) catch - return error.BadSize; - - if (!std.mem.eql(u8, &header.end_of_header, archive_end_of_header)) + if (!std.mem.eql(u8, &header.end_of_header, std.coff.archive_end_of_header)) return error.BadEndOfHeader; return .{ @@ -4787,7 +4758,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo if (!std.mem.eql(u8, signature, std.coff.archive_signature)) return diags.failParse(path, "bad signature", .{}); - var opt_expected_kind: ?Member.Kind = .first_linker; + var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker; var opt_longnames: ?[]const u8 = null; defer if (opt_longnames) |l| gpa.free(l); -- 2.54.0 From 9df2ca0d3382170a2d616d110073e7be25793778 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 53/94] objdump: more coff progress - Linker member selection - Dump implib headers - Dump relocs - Add more info to table headers for easier grepping --- lib/compiler/objdump.zig | 272 ++++++++++++++++++++++++++++++--------- lib/std/coff.zig | 30 +++++ 2 files changed, 238 insertions(+), 64 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index ef426ce9fa8620e85ef52c1fbaf2a55943fb6208..b87116be788d958a0ca136cdfe1bfebefdd02fb3 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -10,10 +10,13 @@ const native_endian = builtin.cpu.arch.endian(); var stdout_buffer: [4000]u8 = undefined; const Options = struct { - input_path: []const u8, + exports: bool, file_headers: bool, + imports: bool, + input_path: []const u8, member_filters: []const []const u8 = &.{}, member_headers: bool, + relocs: bool, section_filters: []const []const u8 = &.{}, section_headers: bool, strings: bool, @@ -30,14 +33,16 @@ pub fn main(init: std.process.Init) !void { var i: usize = 1; - var opt_input_path: ?[]const u8 = null; + var opt_exports: ?bool = null; var opt_file_headers: ?bool = null; + var opt_imports: ?bool = null; + var opt_input_path: ?[]const u8 = null; var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null; var opt_member_headers: ?bool = null; + var opt_relocs: ?bool = null; var opt_section_headers: ?bool = null; var opt_strings: ?bool = null; var opt_symbols: ?bool = null; - var opt_relocs: ?bool = null; var section_filters: std.ArrayList([]const u8) = .empty; var member_filters: std.ArrayList([]const u8) = .empty; while (i < args.len) : (i += 1) { @@ -51,11 +56,17 @@ pub fn main(init: std.process.Init) !void { opt_section_headers = true; opt_symbols = true; opt_relocs = true; + } else if (mem.eql(u8, arg, "--exports")) { + opt_exports = true; } else if (mem.eql(u8, arg, "--file-headers")) { opt_file_headers = true; + } else if (mem.eql(u8, arg, "--imports")) { + opt_imports = true; } else if (mem.startsWith(u8, arg, "--linker-member")) { if (mem.eql(u8, arg["--linker-member".len..], "=1")) opt_linker_member = .first_linker + else if (mem.eql(u8, arg["--linker-member".len..], "=longnames")) + opt_linker_member = .longnames else opt_linker_member = .second_linker; } else if (mem.eql(u8, arg, "--member-headers")) { @@ -84,9 +95,12 @@ pub fn main(init: std.process.Init) !void { const opts: Options = .{ .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), + .exports = opt_exports orelse false, .file_headers = opt_file_headers orelse false, + .imports = opt_imports orelse false, .section_filters = section_filters.items, .section_headers = opt_section_headers orelse false, + .relocs = opt_relocs orelse false, .strings = opt_strings orelse false, .symbols = opt_symbols orelse false, .member_filters = member_filters.items, @@ -129,6 +143,7 @@ fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *I } coff: { const ext = std.fs.path.extension(opts.input_path); + const basename = std.fs.path.basename(opts.input_path); if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) { if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff; try r.discardAll(std.coff.pe_pointer_offset); @@ -141,16 +156,16 @@ fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *I return error.ParseFailure; } - try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)}); - return coff.dumpObject(gpa, opts, true, fr, w); + try w.print("{s}: PE/COFF image\n\n", .{basename}); + return coff.dumpObject(gpa, opts, true, basename, fr, w); } else if (std.mem.eql(u8, ext, ".lib")) { r.fill(std.coff.archive_signature.len) catch break :coff; if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; - try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); + try w.print("{s}: COFF archive\n\n", .{basename}); return coff.dumpArchive(gpa, opts, fr, w); } else if (std.mem.eql(u8, ext, ".obj")) { - try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); - return coff.dumpObject(gpa, opts, false, fr, w); + try w.print("{s}: COFF object\n\n", .{basename}); + return coff.dumpObject(gpa, opts, false, basename, fr, w); } } return error.UnknownFile; @@ -242,7 +257,10 @@ const coff = struct { if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header)) return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header }); - const dump_header = opts.member_headers and filterMatches(opts.member_filters, header.name); + const dump_header = + (opts.member_headers and filterMatches(opts.member_filters, header.name)) or + (opts.linker_member == opt_expected_kind); + if (dump_header) try dumpArchiveHeader(w, &header, @intCast(pos)); @@ -264,11 +282,12 @@ const coff = struct { , .{ expected_kind, num_symbols }); if (opts.linker_member == .first_linker) { - try w.print( - \\Archives symbols ({d}): + try w.writeAll( + \\ + \\Archive symbols: \\& Member Symbol \\ - , .{num_symbols}); + ); const offsets = try r.readAlloc(gpa, num_symbols * 4); defer gpa.free(offsets); @@ -320,11 +339,12 @@ const coff = struct { symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1; if (opts.linker_member == .second_linker) { - try w.print( - \\Archive symbols ({d} members, {d} symbols): + try w.writeAll( + \\ + \\Archive Symbols: \\& Member Symbol \\ - , .{ num_members, num_symbols }); + ); pos = fr.logicalPos(); var symbol_i: u32 = 0; @@ -362,6 +382,21 @@ const coff = struct { opt_longnames = try r.readAlloc(gpa, header.size); if (dump_header) try w.print("{t: >16} type\n", .{expected_kind}); + + if (opts.linker_member == .longnames) { + try w.print( + \\ + \\Longnames (0x{x} bytes): + \\ + , .{opt_longnames.?.len}); + + var lr = Io.Reader.fixed(opt_longnames.?); + while (try lr.takeDelimiter(0)) |str| { + try w.writeAll(str); + try w.writeByte('\n'); + } + try w.writeByte('\n'); + } } opt_expected_kind = null; @@ -390,13 +425,50 @@ const coff = struct { @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little)); const sig = std.mem.readInt(u16, member_sig[2..4], .little); - const is_import = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; + const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; - if (opts.member_headers) { + if (opts.member_headers or (opts.exports and is_imp_lib)) { try dumpArchiveHeader(w, &header, member.offset); - if (is_import) { - try w.writeAll(" Import header type\n"); - // TODO: Dump import header + if (is_imp_lib) { + try w.writeAll("\nImport header:\n"); + + const imp_header = try r.takeStruct(std.coff.ImportHeader, .little); + try dumpHeader(w, std.coff.ImportHeader, &imp_header, struct { + pub fn sig1(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {} + pub fn sig2(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {} + pub fn types(h: *const std.coff.ImportHeader, cw: *Io.Writer) !void { + try cw.print( + \\{t: >16} import_type + \\{t: >16} name_type + \\ + , .{ h.types.type, h.types.name_type }); + } + }); + + const sym_name = (try r.takeDelimiter(0)).?; + const imp_dll = (try r.takeDelimiter(0)).?; + const imp_name = imp_name: switch (imp_header.types.name_type) { + .NAME_NOPREFIX, + .NAME_UNDECORATE, + => |tag| { + var imp_name = std.mem.trimStart(u8, sym_name, "?@_"); + if (tag == .NAME_UNDECORATE) + imp_name = std.mem.sliceTo(imp_name, '@'); + break :imp_name imp_name; + }, + else => sym_name, + }; + + try w.print( + \\ symbol name | {s} + \\ import name | {s} + \\ dll | {s} + \\ + , .{ + sym_name, + imp_name, + imp_dll, + }); } else { try w.writeAll(" COFF object type\n"); } @@ -409,7 +481,7 @@ const coff = struct { opts.symbols) { try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(opts.input_path), header.name }); - try dumpObject(gpa, opts, false, fr, w); + try dumpObject(gpa, opts, false, header.name, fr, w); } } } @@ -418,6 +490,7 @@ const coff = struct { gpa: std.mem.Allocator, opts: *const Options, is_image: bool, + obj_name: []const u8, fr: *Io.File.Reader, w: *Io.Writer, ) !void { @@ -432,6 +505,11 @@ const coff = struct { try w.writeByte('\n'); } + switch (header.machine) { + _ => return failParse(opts, "unknown machine type: {x}", .{header.machine}), + else => {}, + } + if (header.size_of_optional_header > 0) opt_header: { if (!opts.file_headers) { try fr.seekBy(header.size_of_optional_header); @@ -541,59 +619,65 @@ const coff = struct { try w.writeByte('\n'); } - var sections: std.ArrayList(std.coff.SectionHeader) = .empty; + var sections: std.ArrayList(struct { + header: std.coff.SectionHeader, + name: []const u8, + }) = .empty; defer sections.deinit(gpa); - const load_sections = opts.section_headers or opts.symbols; + const load_sections = + opts.section_headers or + opts.symbols or + opts.relocs; + if (load_sections) { if (opts.section_headers) - try w.writeAll( - \\Section Table: + try w.print( + \\Sections ({s}): \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags \\ - ); + , .{obj_name}); try sections.resize(gpa, header.number_of_sections); for (sections.items, 0..) |*section, section_i| { - section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err| + section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err| return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err }); + section.name = headerName(§ion.header.name, string_table) catch |err| switch (err) { + error.Overflow, + error.InvalidCharacter, + => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{ + section.name, + err, + }), + error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{ + section.name, + string_table.len, + }), + }; if (opts.section_headers) { - const name = headerName(§ion.name, string_table) catch |err| switch (err) { - error.Overflow, - error.InvalidCharacter, - => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{ - section.name, - err, - }), - error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{ - section.name, - string_table.len, - }), - }; - - if (!filterMatches(opts.section_filters, name)) continue; - const raw_name = std.mem.sliceTo(§ion.name, 0); + if (!filterMatches(opts.section_filters, section.name)) continue; + const raw_name = std.mem.sliceTo(§ion.header.name, 0); try w.print( "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ", .{ section_i + 1, raw_name, - section.virtual_address, - section.virtual_size, - section.size_of_raw_data, - section.pointer_to_raw_data, - section.pointer_to_relocations, - section.pointer_to_linenumbers, - section.number_of_relocations, - section.number_of_linenumbers, - @as(u32, @bitCast(section.flags)), + section.header.virtual_address, + section.header.virtual_size, + section.header.size_of_raw_data, + section.header.pointer_to_raw_data, + section.header.pointer_to_relocations, + section.header.pointer_to_linenumbers, + section.header.number_of_relocations, + section.header.number_of_linenumbers, + @as(u32, @bitCast(section.header.flags)), }, ); - try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, §ion.flags, 0); - if (name.len > 8) - try w.print("| {s}", .{name}); + try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, §ion.header.flags, 1); + if (section.name.len > 8) + try w.print("| {s}", .{section.name}); try w.writeByte('\n'); } @@ -602,16 +686,21 @@ const coff = struct { if (opts.section_headers) try w.writeByte('\n'); } + var symbol_names: std.ArrayList([]const u8) = .empty; + defer symbol_names.deinit(gpa); + if (opts.relocs) + try symbol_names.ensureUnusedCapacity(gpa, header.number_of_symbols); + if (opts.symbols) { if (header.pointer_to_symbol_table > 0) { fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err| return failParse(opts, "unable to seek to symbol table: {t}", .{err}); - try w.writeAll( - \\Symbol Table: + try w.print( + \\Symbols ({s}): \\ Ord Value Sect Type Storage Name \\ - ); + , .{obj_name}); const symbol_size = std.coff.Symbol.sizeOf(); var symbol_i: u32 = 0; @@ -633,10 +722,17 @@ const coff = struct { const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], .little); if (index >= string_table.len) - return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{ symbol_i, index, string_table.len }); + return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{ + symbol_i, + index, + string_table.len, + }); break :name string_table[index..]; } else &symbol.name, 0); + if (opts.relocs) + symbol_names.appendNTimesAssumeCapacity(name, 1 + symbol.number_of_aux_symbols); + try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value }); try switch (symbol.section_number) { .UNDEFINED => w.writeAll("UNDEF"), @@ -743,18 +839,18 @@ const coff = struct { std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def); const section = §ions.items[section_i]; - if (section_def.number_of_relocations != section.number_of_relocations) { + if (section_def.number_of_relocations != section.header.number_of_relocations) { try w.print( " !! relocation count did not match section header: {d} vs {d}", - .{ section_def.number_of_relocations, section.number_of_relocations }, + .{ section_def.number_of_relocations, section.header.number_of_relocations }, ); continue; } - if (section_def.number_of_linenumbers != section.number_of_linenumbers) { + if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) { try w.print( " !! line number count did not match section header: {d} vs {d}", - .{ section_def.number_of_linenumbers, section.number_of_linenumbers }, + .{ section_def.number_of_linenumbers, section.header.number_of_linenumbers }, ); continue; } @@ -786,6 +882,52 @@ const coff = struct { try w.writeAll("No symbol table found\n"); } } + + if (opts.relocs) { + const relocation_size = std.coff.Relocation.sizeOf(); + + for (sections.items, 0..) |section, section_i| { + if (section.header.pointer_to_relocations == 0) continue; + + try w.print( + \\Relocs for section {x} '{s}' in {s}: + \\ Offset Type Symbol Name + \\ + , .{ section_i + 1, section.name, obj_name }); + + fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err| + return failParse(opts, "unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err }); + + for (0..section.header.number_of_relocations) |reloc_i| { + var reloc: std.coff.Relocation = undefined; + @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size)); + if (native_endian != .little) + std.mem.byteSwapAllFields(std.coff.Relocation, &reloc); + + try w.print("{x:0>8} ", .{reloc.virtual_address}); + switch (header.machine) { + _ => unreachable, + inline else => |m| switch (m.RelocationType()) { + void => try w.writeAll("(unknown arch)"), + else => |RelocationType| try w.print( + "{t: <17} ", + .{@as(RelocationType, @enumFromInt(reloc.type))}, + ), + }, + } + + if (reloc.symbol_table_index >= symbol_names.items.len) + return failParse( + opts, + "reloc {x} in section {x} has out-of-bounds symbol index {x}", + .{ reloc_i, section_i + 1, reloc.symbol_table_index }, + ); + + try w.print("{x: >8} | {s}\n", .{ reloc.symbol_table_index, symbol_names.items[reloc.symbol_table_index] }); + } + try w.writeByte('\n'); + } + } } fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 { @@ -903,12 +1045,14 @@ const usage = \\ -h, --help Print this help and exit \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols \\ --file-headers Display file-format specific headers - \\ --linker-member[=1|2] (Coff) Display contents of the specified linker member (default 2) + \\ --imports Display imported symbols + \\ --exports Display exported symbols + \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified linker member (default 2) \\ --member-headers Display archive member headers \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. \\ --section-headers Display section headers - \\ --strings Display string table + \\ --strings Display string tables \\ --symbols Display symbol tables \\ --relocs Display relocations ; diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 9d7f1c261822ea00adbc222067980e3b30afbbc0..946547d4ae01b41b2f159f97228df48369c89cea 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -1520,6 +1520,36 @@ pub const IMAGE = struct { _, /// AXP 64 (Same as Alpha 64) pub const AXP64: IMAGE.FILE.MACHINE = .ALPHA64; + + pub fn RelocationType(comptime machine: IMAGE.FILE.MACHINE) type { + return switch (machine) { + .AMD64, + => REL.AMD64, + .ARM, + .ARMNT, + => REL.ARM, + .ARM64, + .ARM64EC, + .ARM64X, + => REL.ARM64, + .I386 => REL.I386, + .IA64 => REL.IA64, + .M32R => REL.M32R, + .MIPS16, + .MIPSFPU, + .MIPSFPU16, + => REL.MIPS, + .POWERPC, + .POWERPCFP, + => REL.PPC, + .SH3, + .SH3DSP, + .SH4, + .SH5, + => REL.SH, + else => void, + }; + } }; }; -- 2.54.0 From 815ef725235cd4f5f0caed906bfe868225994f89 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 54/94] objdump: fix --relocs not triggering object dump - Reformat table headers --- lib/compiler/objdump.zig | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index b87116be788d958a0ca136cdfe1bfebefdd02fb3..124705855ff1db84f54948f17ce8d2c9e08b4ab7 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -477,6 +477,7 @@ const coff = struct { if (opts.section_headers or opts.file_headers or + opts.relocs or opts.strings or opts.symbols) { @@ -633,7 +634,7 @@ const coff = struct { if (load_sections) { if (opts.section_headers) try w.print( - \\Sections ({s}): + \\Sections in {s}: \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags \\ , .{obj_name}); @@ -691,16 +692,17 @@ const coff = struct { if (opts.relocs) try symbol_names.ensureUnusedCapacity(gpa, header.number_of_symbols); - if (opts.symbols) { + if (opts.symbols or opts.relocs) { if (header.pointer_to_symbol_table > 0) { fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err| return failParse(opts, "unable to seek to symbol table: {t}", .{err}); - try w.print( - \\Symbols ({s}): - \\ Ord Value Sect Type Storage Name - \\ - , .{obj_name}); + if (opts.symbols) + try w.print( + \\Symbols in {s}: + \\ Ord Value Sect Type Storage Name + \\ + , .{obj_name}); const symbol_size = std.coff.Symbol.sizeOf(); var symbol_i: u32 = 0; @@ -733,6 +735,9 @@ const coff = struct { if (opts.relocs) symbol_names.appendNTimesAssumeCapacity(name, 1 + symbol.number_of_aux_symbols); + if (!opts.symbols) + continue; + try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value }); try switch (symbol.section_number) { .UNDEFINED => w.writeAll("UNDEF"), @@ -877,8 +882,8 @@ const coff = struct { } } - try w.writeByte('\n'); - } else { + if (opts.symbols) try w.writeByte('\n'); + } else if (opts.symbols) { try w.writeAll("No symbol table found\n"); } } -- 2.54.0 From 32377bb73cc70dfd141629eef0e5238b5debeb57 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 55/94] objdump: show section number in relocation table --- lib/compiler/objdump.zig | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 124705855ff1db84f54948f17ce8d2c9e08b4ab7..f920d7e0f564a3b56ec1c95c02e91d8c11817782 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -687,10 +687,13 @@ const coff = struct { if (opts.section_headers) try w.writeByte('\n'); } - var symbol_names: std.ArrayList([]const u8) = .empty; - defer symbol_names.deinit(gpa); + var symbols: std.ArrayList(struct { + name: []const u8, + section_number: std.coff.SectionNumber, + }) = .empty; + defer symbols.deinit(gpa); if (opts.relocs) - try symbol_names.ensureUnusedCapacity(gpa, header.number_of_symbols); + try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); if (opts.symbols or opts.relocs) { if (header.pointer_to_symbol_table > 0) { @@ -733,7 +736,10 @@ const coff = struct { } else &symbol.name, 0); if (opts.relocs) - symbol_names.appendNTimesAssumeCapacity(name, 1 + symbol.number_of_aux_symbols); + symbols.appendNTimesAssumeCapacity(.{ + .name = name, + .section_number = symbol.section_number, + }, 1 + symbol.number_of_aux_symbols); if (!opts.symbols) continue; @@ -896,7 +902,7 @@ const coff = struct { try w.print( \\Relocs for section {x} '{s}' in {s}: - \\ Offset Type Symbol Name + \\ Offset Type Symbol -> Sect Name \\ , .{ section_i + 1, section.name, obj_name }); @@ -921,14 +927,19 @@ const coff = struct { }, } - if (reloc.symbol_table_index >= symbol_names.items.len) + if (reloc.symbol_table_index >= symbols.items.len) return failParse( opts, "reloc {x} in section {x} has out-of-bounds symbol index {x}", .{ reloc_i, section_i + 1, reloc.symbol_table_index }, ); - try w.print("{x: >8} | {s}\n", .{ reloc.symbol_table_index, symbol_names.items[reloc.symbol_table_index] }); + try w.print("{x: >8} ", .{reloc.symbol_table_index}); + + const sym = &symbols.items[reloc.symbol_table_index]; + try sectionNumberString(sym.section_number, w); + + try w.print(" | {s}\n", .{sym.name}); } try w.writeByte('\n'); } -- 2.54.0 From 62892173839f4741e02b15dea117c9245bfe930b Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 56/94] objdump: coff --exports --- lib/compiler/objdump.zig | 379 ++++++++++++++++++++++++++++++--------- 1 file changed, 293 insertions(+), 86 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index f920d7e0f564a3b56ec1c95c02e91d8c11817782..8d9474f5c27450cd4a3eed6f0178280d7c47abcf 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -21,6 +21,7 @@ const Options = struct { section_headers: bool, strings: bool, symbols: bool, + tls: bool, // Coff-specific linker_member: ?std.coff.ArchiveMemberHeader.Kind, @@ -43,6 +44,7 @@ pub fn main(init: std.process.Init) !void { var opt_section_headers: ?bool = null; var opt_strings: ?bool = null; var opt_symbols: ?bool = null; + var opt_tls: ?bool = null; var section_filters: std.ArrayList([]const u8) = .empty; var member_filters: std.ArrayList([]const u8) = .empty; while (i < args.len) : (i += 1) { @@ -83,6 +85,8 @@ pub fn main(init: std.process.Init) !void { opt_strings = true; } else if (mem.eql(u8, arg, "--symbols")) { opt_symbols = true; + } else if (mem.eql(u8, arg, "--tls")) { + opt_tls = true; } else { fatal("unrecognized argument: {s}", .{arg}); } @@ -98,14 +102,15 @@ pub fn main(init: std.process.Init) !void { .exports = opt_exports orelse false, .file_headers = opt_file_headers orelse false, .imports = opt_imports orelse false, + .linker_member = opt_linker_member, + .member_filters = member_filters.items, + .member_headers = opt_member_headers orelse false, .section_filters = section_filters.items, .section_headers = opt_section_headers orelse false, .relocs = opt_relocs orelse false, .strings = opt_strings orelse false, .symbols = opt_symbols orelse false, - .member_filters = member_filters.items, - .member_headers = opt_member_headers orelse false, - .linker_member = opt_linker_member, + .tls = opt_tls orelse false, }; var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err| @@ -202,6 +207,21 @@ const wasm = struct { }; const coff = struct { + const DIRECTORY_ENTRY = std.coff.IMAGE.DIRECTORY_ENTRY; + + const Section = struct { + header: std.coff.SectionHeader, + name: []const u8, + + fn rvaFileOffset(section: *const Section, rva: u32) !u32 { + if (rva < section.header.virtual_address or + rva >= section.header.virtual_address + section.header.size_of_raw_data) + return error.OutOfBounds; + + return section.header.pointer_to_raw_data + (rva - section.header.virtual_address); + } + }; + const ArchiveHeader = struct { name: []const u8, date: u40, @@ -308,8 +328,6 @@ const coff = struct { if (!std.mem.eql(u8, header.name, "/")) return failParse(opts, "expected second linker member, found '{s}'", .{header.name}); - // TODO: Figure out what endianness is actually used, there are no headers to say yet? - const num_members = try r.takeInt(u32, .little); pos = fr.logicalPos(); if (pos + num_members * @sizeOf(u32) > member_end) @@ -511,16 +529,22 @@ const coff = struct { else => {}, } - if (header.size_of_optional_header > 0) opt_header: { - if (!opts.file_headers) { + var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined; + const needs_data_dirs = + opts.exports or + opts.imports or + opts.tls; + + const data_dirs = if (header.size_of_optional_header > 0) data_dirs: { + if (!opts.file_headers and !needs_data_dirs) { try fr.seekBy(header.size_of_optional_header); - break :opt_header; + break :data_dirs &.{}; } - try w.writeAll("COFF Optional Header:\n"); + if (opts.file_headers) try w.writeAll("COFF Optional Header:\n"); const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); const num_directory_entries = switch (magic) { - inline .PE32, .@"PE32+" => |v| data_dirs: { + inline .PE32, .@"PE32+" => |v| num_data_dirs: { const OptionalHeader = if (v == .PE32) std.coff.OptionalHeader.PE32 else @@ -529,63 +553,71 @@ const coff = struct { const optional_header = r.takeStruct(OptionalHeader, .little) catch |err| return failParse(opts, "unable to read optional header: {t}", .{err}); - try dumpHeader(w, OptionalHeader, &optional_header, struct { - pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { - const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; - try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); - } + if (opts.file_headers) { + try dumpHeader(w, OptionalHeader, &optional_header, struct { + pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; + try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); + } - pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { - const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; - try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); - } + pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; + try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); + } - pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version); - } - pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {} + pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version); + } + pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {} - pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField( - cw, - "operating_system_version", - h.major_operating_system_version, - h.minor_operating_system_version, - ); - } - pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField( + cw, + "operating_system_version", + h.major_operating_system_version, + h.minor_operating_system_version, + ); + } + pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {} - pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version); - } - pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version); + } + pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {} - pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version); - } - pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} - }); - try w.writeByte('\n'); + pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version); + } + pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + }); + try w.writeByte('\n'); + } - break :data_dirs optional_header.number_of_rva_and_sizes; + break :num_data_dirs optional_header.number_of_rva_and_sizes; }, else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), }; - try w.writeAll("Data Directories:\n"); + if (opts.file_headers) try w.writeAll("Data Directories:\n"); for (0..num_directory_entries) |dir_i| { const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); - try w.print( - "{x: >16} {x: >8} {t}\n", - .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, - ); + if (dir_i < known_dirs.len) + known_dirs[dir_i] = dir; + + if (opts.file_headers) + try w.print( + "{x: >16} {x: >8} {t}\n", + .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, + ); } - try w.writeByte('\n'); + if (opts.file_headers) try w.writeByte('\n'); + + break :data_dirs known_dirs[0..@min(known_dirs.len, num_directory_entries)]; } else if (is_image) { return failParse(opts, "image did not contain an optional header", .{}); - } + } else &.{}; // Section names in images don't use the string table, as they must fit inline in the header const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; @@ -620,16 +652,15 @@ const coff = struct { try w.writeByte('\n'); } - var sections: std.ArrayList(struct { - header: std.coff.SectionHeader, - name: []const u8, - }) = .empty; + var sections: std.ArrayList(Section) = .empty; defer sections.deinit(gpa); + var sections_with_data: u16 = 0; const load_sections = opts.section_headers or opts.symbols or - opts.relocs; + opts.relocs or + needs_data_dirs; if (load_sections) { if (opts.section_headers) @@ -656,11 +687,12 @@ const coff = struct { }), }; + sections_with_data += @intFromBool(section.header.size_of_raw_data > 0); if (opts.section_headers) { if (!filterMatches(opts.section_filters, section.name)) continue; const raw_name = std.mem.sliceTo(§ion.header.name, 0); try w.print( - "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ", + "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} |", .{ section_i + 1, raw_name, @@ -676,7 +708,7 @@ const coff = struct { }, ); - try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, §ion.header.flags, 1); + try dumpFlags(w, "{s}", std.coff.SectionHeader.Flags, §ion.header.flags, 1); if (section.name.len > 8) try w.print("| {s}", .{section.name}); @@ -790,10 +822,7 @@ const coff = struct { (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef"))) { try w.writeAll("TODO bf / ef aux symbol"); - } else if (symbol.storage_class == .EXTERNAL and - symbol.section_number == .UNDEFINED and - symbol.value == 0) - { + } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) { if (symbol.value != 0) return failParse( opts, @@ -815,6 +844,10 @@ const coff = struct { // TODO + try w.print(" Weak External [falls back to {x:0>8} via {t}]", .{ + weak_external.tag_index, + weak_external.flag, + }); } else if (symbol.storage_class == .FILE) { if (!std.mem.eql(u8, name, ".file")) { try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i }); @@ -824,6 +857,7 @@ const coff = struct { var file: std.coff.FileDefinition = undefined; @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]); + // TODO _ = file.getFileName(); } else if (symbol.storage_class == .STATIC and symbol.type == std.coff.SymType{ @@ -934,16 +968,205 @@ const coff = struct { .{ reloc_i, section_i + 1, reloc.symbol_table_index }, ); - try w.print("{x: >8} ", .{reloc.symbol_table_index}); - const sym = &symbols.items[reloc.symbol_table_index]; - try sectionNumberString(sym.section_number, w); - - try w.print(" | {s}\n", .{sym.name}); + try w.print("{x: >8} {f} | {s}\n", .{ + reloc.symbol_table_index, + fmtSectionNumber(sym.section_number), + sym.name, + }); } try w.writeByte('\n'); } } + + // Sections indices with raw data, sorted by RVA + const rva_index = if (needs_data_dirs) rva_index: { + const rva_index = try gpa.alloc(u16, sections_with_data); + var indices_i: u16 = 0; + for (sections.items, 0..) |*section, i| { + if (section.header.size_of_raw_data == 0) continue; + rva_index[indices_i] = @intCast(i); + indices_i += 1; + } + + const Context = struct { + indices: []u16, + sections: []const Section, + + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + return ctx.sections[ctx.indices[lhs]].header.virtual_address < + ctx.sections[ctx.indices[rhs]].header.virtual_address; + } + + pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { + std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]); + } + }; + + std.sort.pdqContext(0, rva_index.len, Context{ + .indices = rva_index, + .sections = sections.items, + }); + + break :rva_index rva_index; + } else &.{}; + defer gpa.free(rva_index); + + if (opts.exports) { + if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .EXPORT)) |section_index| { + const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err| + return failParse(opts, "unable to read export directory: {t}", .{err}); + + try w.print("Export directory:\n", .{}); + try dumpHeader(w, std.coff.ExportDirectoryTable, &export_dir, struct { + pub fn major_version(h: *const std.coff.ExportDirectoryTable, cw: *Io.Writer) !void { + try dumpVersionField(cw, "version", h.major_version, h.minor_version); + } + pub fn minor_version(_: *const std.coff.ExportDirectoryTable, _: *Io.Writer) !void {} + }); + + const section = sections.items[section_index]; + const name_loc = section.rvaFileOffset(export_dir.name_rva) catch + return failParse( + opts, + "export name rva 0x{x} was not within the export section", + .{export_dir.name_rva}, + ); + + const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch + return failParse( + opts, + "export address table rva 0x{x} was not within the export section", + .{export_dir.export_address_table_rva}, + ); + + const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch + return failParse( + opts, + "export name pointer table rva 0x{x} was not within the export section", + .{export_dir.name_pointer_table_rva}, + ); + + const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch + return failParse( + opts, + "export ordinal table rva 0x{x} was not within the export section", + .{export_dir.ordinal_table_rva}, + ); + + // All the variable length fields should be contained within this directory. + // Read it entirely to avoid needing to seek per-name when iterating. + const dir = data_dirs[@intFromEnum(DIRECTORY_ENTRY.EXPORT)]; + const dir_end_rva = dir.virtual_address + dir.size; + const dir_loc = fr.logicalPos(); + const dir_slice = try r.readAlloc(gpa, dir.size); + defer gpa.free(dir_slice); + + const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0); + try w.print( + \\ + \\Exports from {s}: + \\ Ord Hint RVA Name + \\ + , .{dll_name}); + + const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)]; + const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)]; + const addrs = dir_slice[eat_loc - dir_loc ..][0 .. export_dir.number_of_entries * @sizeOf(u32)]; + const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable); + for (0..export_dir.number_of_names) |name_i| { + const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little); + const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little); + const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little); + + try w.print("{x: >4} {x: >4} ", .{ export_dir.ordinal_base + ord, name_i }); + const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva; + if (is_forwarder) { + try w.writeAll("forwards"); + } else { + try w.print("{x: >8}", .{addr}); + } + + const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0); + try w.print(" | {s}", .{name}); + if (is_forwarder) + try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)}); + try w.writeByte('\n'); + } + } + } + + if (opts.imports) { + if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .IMPORT)) |_| { + // TODO + } + } + + if (opts.tls) { + if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .TLS)) |_| { + // TODO + } + } + } + + fn seekToDataDirectory( + opts: *const Options, + fr: *Io.File.Reader, + w: *Io.Writer, + rva_index: []const u16, + sections: []const Section, + data_dirs: []const std.coff.ImageDataDirectory, + entry: DIRECTORY_ENTRY, + ) !?u16 { + if (@intFromEnum(entry) < data_dirs.len) { + const rva = data_dirs[@intFromEnum(entry)].virtual_address; + const section_index = sectionContainingRva(rva_index, sections, rva) orelse + return failParse( + opts, + "{t} directory rva 0x{x} was not found in any section", + .{ entry, rva }, + ); + + const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable; + fr.seekTo(file_offset) catch |err| + return failParse( + opts, + "unable to seek to {t} directory at offset 0x{x}: {t}", + .{ entry, file_offset, err }, + ); + + return section_index; + } + + try w.print("{t} directory was not present in optional header\n", .{entry}); + return null; + } + + fn sectionContainingRva( + /// Indices into `sections` sorted by rva + indices: []const u16, + sections: []const Section, + rva: u32, + ) ?u16 { + const Context = struct { + rva: u32, + sections: []const Section, + + fn order(ctx: @This(), section_index: u16) std.math.Order { + const h = &ctx.sections[section_index].header; + const start = h.virtual_address; + if (ctx.rva < start) return .lt; + const end = h.virtual_address + h.size_of_raw_data; + if (ctx.rva >= end) return .gt; + return .eq; + } + }; + + const index = std.sort.binarySearch(u16, indices, Context{ + .rva = rva, + .sections = sections, + }, Context.order) orelse return null; + return @intCast(index); } fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 { @@ -956,21 +1179,6 @@ const coff = struct { } else std.mem.sliceTo(raw, 0); } - fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) { - return .{ .data = sym_type }; - } - - fn symbolTypeString(sym_type: std.coff.SymType, w: *std.Io.Writer) std.Io.Writer.Error!void { - try w.print("{t: >5}", .{sym_type.base_type}); - if (try switch (sym_type.complex_type) { - .NULL => " ", - .POINTER => "* ", - .FUNCTION => "()", - .ARRAY => "[]", - else => null, - }) |suffix| try .printAll(suffix) else w.print("{x}", .{sym_type.complex_type}); - } - fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) { return .{ .data = section_number }; } @@ -1003,8 +1211,6 @@ const coff = struct { fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void { try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); - - // TODO: Date formatter try dumpHeader(w, ArchiveHeader, header, struct { pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {} pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void { @@ -1065,10 +1271,11 @@ const usage = \\ --exports Display exported symbols \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified linker member (default 2) \\ --member-headers Display archive member headers - \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. - \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. + \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times. + \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times. + \\ --relocs Display relocations \\ --section-headers Display section headers \\ --strings Display string tables \\ --symbols Display symbol tables - \\ --relocs Display relocations + \\ --tls Display TLS information ; -- 2.54.0 From 97bf880486c6bd0811f675c61d7675632fd1722d Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 57/94] objdump: coff --imports --- lib/compiler/objdump.zig | 137 +++++++++++++++++++++++++++++++++++++-- lib/std/coff.zig | 66 ++++++------------- src/link/Coff.zig | 27 +------- 3 files changed, 153 insertions(+), 77 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 8d9474f5c27450cd4a3eed6f0178280d7c47abcf..cc96f49071c9b638ca009b3000abd971f22e666b 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -535,10 +535,10 @@ const coff = struct { opts.imports or opts.tls; - const data_dirs = if (header.size_of_optional_header > 0) data_dirs: { + const data_dirs, const magic = if (header.size_of_optional_header > 0) optional_header: { if (!opts.file_headers and !needs_data_dirs) { try fr.seekBy(header.size_of_optional_header); - break :data_dirs &.{}; + break :optional_header .{ &.{}, null }; } if (opts.file_headers) try w.writeAll("COFF Optional Header:\n"); @@ -614,10 +614,10 @@ const coff = struct { } if (opts.file_headers) try w.writeByte('\n'); - break :data_dirs known_dirs[0..@min(known_dirs.len, num_directory_entries)]; + break :optional_header .{ known_dirs[0..@min(known_dirs.len, num_directory_entries)], magic }; } else if (is_image) { return failParse(opts, "image did not contain an optional header", .{}); - } else &.{}; + } else .{ &.{}, null }; // Section names in images don't use the string table, as they must fit inline in the header const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; @@ -1098,7 +1098,134 @@ const coff = struct { if (opts.imports) { if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .IMPORT)) |_| { - // TODO + const Entry = std.coff.ImportDirectoryEntry; + var directory_entries: std.ArrayList(Entry) = .empty; + defer directory_entries.deinit(gpa); + while (true) { + const entry = r.takeStruct(Entry, .little) catch |err| + return failParse( + opts, + "unable to read import directory entry {x}: {t}", + .{ directory_entries.items.len, err }, + ); + + if (std.mem.allEqual(u8, std.mem.asBytes(&entry), 0)) break; + (try directory_entries.addOne(gpa)).* = entry; + } + + for (directory_entries.items) |entry| { + const name_section = sectionContainingRva(rva_index, sections.items, entry.name_rva) orelse + return failParse( + opts, + "import directory entry name rva 0x{x} was not found in any section", + .{entry.name_rva}, + ); + + const name_loc = sections.items[name_section].rvaFileOffset(entry.name_rva) catch unreachable; + fr.seekTo(name_loc) catch |err| + return failParse( + opts, + "unable to seek to import directory entry name at 0x{x}: {t}", + .{ name_loc, err }, + ); + + const dll_name = (try r.takeDelimiter(0)).?; + + try w.print("Import table entry for {s}:\n", .{dll_name}); + try dumpHeader(w, Entry, &entry, struct {}); + + try w.print( + \\ + \\ Ord Hint Name + \\ + , .{}); + + const ilt_section = sectionContainingRva( + rva_index, + sections.items, + entry.import_lookup_table_rva, + ) orelse + return failParse( + opts, + "import directory entry ilt rva 0x{x} was not found in any section", + .{entry.import_lookup_table_rva}, + ); + + const ilt_loc = sections.items[ilt_section].rvaFileOffset( + entry.import_lookup_table_rva, + ) catch unreachable; + fr.seekTo(ilt_loc) catch |err| + return failParse( + opts, + "unable to seek to import directory ilt at 0x{x}: {t}", + .{ ilt_loc, err }, + ); + + switch (magic.?) { + _ => try w.writeAll("(unknown magic)"), + inline else => |m| { + const TableEntry = std.coff.ImportLookupTableEntry(m); + const null_entry: TableEntry = @bitCast(@as(@typeInfo(TableEntry).@"struct".backing_integer.?, 0)); + + var ilt_entries: std.ArrayList(TableEntry) = .empty; + defer ilt_entries.deinit(gpa); + while (true) { + const table_entry = r.takeStruct(TableEntry, .little) catch |err| + return failParse( + opts, + "unable to read ilt entry {s}:{x}: {t}", + .{ dll_name, ilt_entries.items.len, err }, + ); + if (table_entry == null_entry) break; + (try ilt_entries.addOne(gpa)).* = table_entry; + } + + for (ilt_entries.items, 0..) |ilt_entry, ilt_entry_i| { + if (ilt_entry.is_ordinal) { + try w.print("{x: >4}", .{ilt_entry.payload.ordinal.ordinal}); + } else { + const hint_section = sectionContainingRva( + rva_index, + sections.items, + ilt_entry.payload.hint_name_rva, + ) orelse + return failParse( + opts, + "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section", + .{ ilt_entry_i, ilt_entry.payload.hint_name_rva }, + ); + + const hint_loc = sections.items[hint_section].rvaFileOffset( + ilt_entry.payload.hint_name_rva, + ) catch unreachable; + fr.seekTo(hint_loc) catch |err| + return failParse( + opts, + "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}", + .{ ilt_entry_i, hint_loc, err }, + ); + + const hint = r.takeInt(u16, .little) catch |err| + return failParse( + opts, + "unable to read import directory ilt entry 0x{x}'s hint: {t}", + .{ ilt_entry_i, err }, + ); + + const name = r.takeDelimiter(0) catch |err| + return failParse( + opts, + "unable to read import directory ilt entry 0x{x}'s name: {t}", + .{ ilt_entry_i, err }, + ); + + try w.print(" {x: >4} | {s}\n", .{ hint, name.? }); + } + } + try w.writeByte('\n'); + }, + } + } } } diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 946547d4ae01b41b2f159f97228df48369c89cea..15f5a677ba693b29551fe933a326fe889fd06a3b 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -395,56 +395,28 @@ pub const ImportDirectoryEntry = extern struct { import_address_table_rva: u32, }; -pub const ImportLookupEntry32 = struct { - pub const ByName = packed struct(u32) { - name_table_rva: u31, - flag: u1 = 0, +pub fn ImportLookupTableEntry(comptime magic: std.coff.OptionalHeader.Magic) type { + const Payload = packed union(u31) { + ordinal: packed struct(u31) { + ordinal: u16, + _: u15 = 0, + }, + hint_name_rva: u31, }; - pub const ByOrdinal = packed struct(u32) { - ordinal_number: u16, - unused: u15 = 0, - flag: u1 = 1, + 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 mask = 0x80000000; - - pub fn getImportByName(raw: u32) ?ByName { - if (mask & raw != 0) return null; - return @as(ByName, @bitCast(raw)); - } - - pub fn getImportByOrdinal(raw: u32) ?ByOrdinal { - if (mask & raw == 0) return null; - return @as(ByOrdinal, @bitCast(raw)); - } -}; - -pub const ImportLookupEntry64 = struct { - pub const ByName = packed struct(u64) { - name_table_rva: u31, - unused: u32 = 0, - flag: u1 = 0, - }; - - pub const ByOrdinal = packed struct(u64) { - ordinal_number: u16, - unused: u47 = 0, - flag: u1 = 1, - }; - - const mask = 0x8000000000000000; - - pub fn getImportByName(raw: u64) ?ByName { - if (mask & raw != 0) return null; - return @as(ByName, @bitCast(raw)); - } - - pub fn getImportByOrdinal(raw: u64) ?ByOrdinal { - if (mask & raw == 0) return null; - return @as(ByOrdinal, @bitCast(raw)); - } -}; +} /// Every name ends with a NULL byte. IF the NULL byte does not fall on /// 2byte boundary, the entry structure is padded to ensure 2byte alignment. diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 20221e09dfa23550543f025862839b7067133e3b..1a78ab79711428ca9bc59d5015e4377498e07d2b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -730,29 +730,6 @@ pub const ImportTable = struct { 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, @@ -6316,7 +6293,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { switch (addr_info.magic) { _ => unreachable, inline .PE32, .@"PE32+" => |ct_magic| { - const Entry = ImportTable.TableEntry(ct_magic); + const Entry = std.coff.ImportLookupTableEntry(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 = .{ @@ -6675,7 +6652,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { switch (magic) { _ => unreachable, inline .PE32, .@"PE32+" => |ct_magic| { - const Entry = ImportTable.TableEntry(ct_magic); + const Entry = std.coff.ImportLookupTableEntry(ct_magic); const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice)); const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice)); -- 2.54.0 From b2eee06bd67e720014809ade89b0b054865b0040 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 58/94] objdump: coff --tls --- lib/compiler/objdump.zig | 130 ++++++++++++++++++++++++++++++++++----- lib/std/coff.zig | 30 +++++++++ 2 files changed, 145 insertions(+), 15 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index cc96f49071c9b638ca009b3000abd971f22e666b..4ff2f65b2279e35ec1e1cdbffaa3ff6727e55c68 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -535,15 +535,21 @@ const coff = struct { opts.imports or opts.tls; - const data_dirs, const magic = if (header.size_of_optional_header > 0) optional_header: { + const ImageInfo = struct { + data_dirs: []const std.coff.ImageDataDirectory, + magic: std.coff.OptionalHeader.Magic, + image_base: u64, + }; + + const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: { if (!opts.file_headers and !needs_data_dirs) { try fr.seekBy(header.size_of_optional_header); - break :optional_header .{ &.{}, null }; + break :image_info null; } if (opts.file_headers) try w.writeAll("COFF Optional Header:\n"); const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); - const num_directory_entries = switch (magic) { + const num_directory_entries, const image_base = switch (magic) { inline .PE32, .@"PE32+" => |v| num_data_dirs: { const OptionalHeader = if (v == .PE32) std.coff.OptionalHeader.PE32 @@ -593,7 +599,10 @@ const coff = struct { try w.writeByte('\n'); } - break :num_data_dirs optional_header.number_of_rva_and_sizes; + break :num_data_dirs .{ + optional_header.number_of_rva_and_sizes, + optional_header.image_base, + }; }, else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), }; @@ -614,10 +623,14 @@ const coff = struct { } if (opts.file_headers) try w.writeByte('\n'); - break :optional_header .{ known_dirs[0..@min(known_dirs.len, num_directory_entries)], magic }; + break :image_info .{ + .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)], + .magic = magic, + .image_base = image_base, + }; } else if (is_image) { return failParse(opts, "image did not contain an optional header", .{}); - } else .{ &.{}, null }; + } else null; // Section names in images don't use the string table, as they must fit inline in the header const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; @@ -1013,7 +1026,7 @@ const coff = struct { defer gpa.free(rva_index); if (opts.exports) { - if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .EXPORT)) |section_index| { + if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, image_info.?.data_dirs, .EXPORT)) |section_index| { const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err| return failParse(opts, "unable to read export directory: {t}", .{err}); @@ -1056,7 +1069,7 @@ const coff = struct { // All the variable length fields should be contained within this directory. // Read it entirely to avoid needing to seek per-name when iterating. - const dir = data_dirs[@intFromEnum(DIRECTORY_ENTRY.EXPORT)]; + const dir = image_info.?.data_dirs[@intFromEnum(DIRECTORY_ENTRY.EXPORT)]; const dir_end_rva = dir.virtual_address + dir.size; const dir_loc = fr.logicalPos(); const dir_slice = try r.readAlloc(gpa, dir.size); @@ -1097,7 +1110,15 @@ const coff = struct { } if (opts.imports) { - if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .IMPORT)) |_| { + if (try seekToDataDirectory( + opts, + fr, + w, + rva_index, + sections.items, + image_info.?.data_dirs, + .IMPORT, + )) |_| { const Entry = std.coff.ImportDirectoryEntry; var directory_entries: std.ArrayList(Entry) = .empty; defer directory_entries.deinit(gpa); @@ -1114,14 +1135,20 @@ const coff = struct { } for (directory_entries.items) |entry| { - const name_section = sectionContainingRva(rva_index, sections.items, entry.name_rva) orelse + const name_section = sectionContainingRva( + rva_index, + sections.items, + entry.name_rva, + ) orelse return failParse( opts, "import directory entry name rva 0x{x} was not found in any section", .{entry.name_rva}, ); - const name_loc = sections.items[name_section].rvaFileOffset(entry.name_rva) catch unreachable; + const name_loc = sections.items[name_section].rvaFileOffset( + entry.name_rva, + ) catch unreachable; fr.seekTo(name_loc) catch |err| return failParse( opts, @@ -1161,7 +1188,7 @@ const coff = struct { .{ ilt_loc, err }, ); - switch (magic.?) { + switch (image_info.?.magic) { _ => try w.writeAll("(unknown magic)"), inline else => |m| { const TableEntry = std.coff.ImportLookupTableEntry(m); @@ -1230,8 +1257,79 @@ const coff = struct { } if (opts.tls) { - if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .TLS)) |_| { - // TODO + if (try seekToDataDirectory( + opts, + fr, + w, + rva_index, + sections.items, + image_info.?.data_dirs, + .TLS, + )) |_| { + switch (image_info.?.magic) { + _ => try w.writeAll("(unknown magic)"), + inline else => |m| { + const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m); + const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err| + return failParse(opts, "unable to read tls directory: {t}", .{err}); + + try w.writeAll("TLS Directory:\n"); + try dumpHeader(w, TlsDirectoryEntry, &tls_entry, struct {}); + + try w.writeAll(" | "); + if (tls_entry.characteristics.alignment == .NONE) { + try w.writeAll("Alignment not specified"); + } else { + try w.print( + "Alignment: {d}", + .{tls_entry.characteristics.alignment.toByteUnits().?}, + ); + } + + try w.writeAll( + \\ + \\ + \\TLS Callbacks: + \\ Address + \\ + ); + + const callbacks_rva: u32 = @intCast(tls_entry.callbacks_va - image_info.?.image_base); + const section_index = sectionContainingRva( + rva_index, + sections.items, + callbacks_rva, + ) orelse + return failParse( + opts, + "tls callbacks rva 0x{x} was not found in any section", + .{callbacks_rva}, + ); + + const callbacks_loc = sections.items[section_index] + .rvaFileOffset(callbacks_rva) catch unreachable; + + fr.seekTo(callbacks_loc) catch |err| + return failParse( + opts, + "unable to seek to tls callbacks array at offset 0x{x}: {t}", + .{ callbacks_loc, err }, + ); + + while (true) { + const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err| + return failParse( + opts, + "unable to read tls callbacks array: {t}", + .{err}, + ); + + try w.print("{x: >16} \n", .{callback_va}); + if (callback_va == 0) break; + } + try w.writeByte('\n'); + }, + } } } } @@ -1245,8 +1343,10 @@ const coff = struct { data_dirs: []const std.coff.ImageDataDirectory, entry: DIRECTORY_ENTRY, ) !?u16 { - if (@intFromEnum(entry) < data_dirs.len) { + if (@intFromEnum(entry) < data_dirs.len) blk: { const rva = data_dirs[@intFromEnum(entry)].virtual_address; + if (rva == 0) break :blk; + const section_index = sectionContainingRva(rva_index, sections, rva) orelse return failParse( opts, diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 15f5a677ba693b29551fe933a326fe889fd06a3b..ed33ef21bc3393a3d080fc95e23c8ee4a6063b85 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -373,6 +373,36 @@ pub const DebugType = enum(u32) { _, }; +pub fn TlsDirectoryEntry(comptime magic: std.coff.OptionalHeader.Magic) type { + return switch (magic) { + _ => comptime unreachable, + .PE32 => extern struct { + raw_data_start_va: u32, + raw_data_end_va: u32, + tls_index_va: u32, + callbacks_va: u32, + size_of_zero_fill: u32, + characteristics: packed struct(u32) { + _reserved_0: u19, + alignment: SectionHeader.Flags.Align, + _reserved_1: u9, + }, + }, + .@"PE32+" => extern struct { + raw_data_start_va: u64, + raw_data_end_va: u64, + tls_index_va: u64, + callbacks_va: u64, + size_of_zero_fill: u32, + characteristics: packed struct(u32) { + _reserved_0: u19, + alignment: SectionHeader.Flags.Align, + _reserved_1: u9, + }, + }, + }; +} + pub const ImportDirectoryEntry = extern struct { /// The RVA of the import lookup table. /// This table contains a name or ordinal for each import. -- 2.54.0 From c6d0c68141ebd8059bf5e7cdc5f1b4117e62e64a Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 59/94] objdump: fixup incorrect dumping of implib objects --- lib/compiler/objdump.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 4ff2f65b2279e35ec1e1cdbffaa3ff6727e55c68..ddb88fa104d7f88754e97718e77f9325718d7eb4 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -413,8 +413,9 @@ const coff = struct { try w.writeAll(str); try w.writeByte('\n'); } - try w.writeByte('\n'); } + + try w.writeByte('\n'); } opt_expected_kind = null; @@ -444,7 +445,6 @@ const coff = struct { const sig = std.mem.readInt(u16, member_sig[2..4], .little); const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; - if (opts.member_headers or (opts.exports and is_imp_lib)) { try dumpArchiveHeader(w, &header, member.offset); if (is_imp_lib) { @@ -493,6 +493,7 @@ const coff = struct { try w.writeByte('\n'); } + if (is_imp_lib) continue; if (opts.section_headers or opts.file_headers or opts.relocs or @@ -678,7 +679,7 @@ const coff = struct { if (load_sections) { if (opts.section_headers) try w.print( - \\Sections in {s}: + \\Sections in '{s}': \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags \\ , .{obj_name}); @@ -747,7 +748,7 @@ const coff = struct { if (opts.symbols) try w.print( - \\Symbols in {s}: + \\Symbols in '{s}': \\ Ord Value Sect Type Storage Name \\ , .{obj_name}); -- 2.54.0 From ab46b8235450cd1d8cb1a483859c2265588a1863 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 60/94] test: Starting work on the new linker tests --- build.zig | 9 +++ test/link.zig | 17 +++++ test/src/Link.zig | 159 ++++++++++++++++++++++++++++++++++++++++++++++ test/tests.zig | 108 +++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 test/link.zig create mode 100644 test/src/Link.zig diff --git a/build.zig b/build.zig index e1bc5ff81c92e252d23bf92e1ff2c783f7805d6d..d2585639bcad8f272cd7ef95abd9e56dc759784f 100644 --- a/build.zig +++ b/build.zig @@ -629,6 +629,15 @@ pub fn build(b: *std.Build) !void { .skip_llvm = skip_llvm, .max_rss = 3_300_000_000, })); + test_step.dependOn(tests.addLinkTests(b, .{ + .test_target_filters = test_target_filters, + .test_filters = test_filters, + .optimize_modes = optimize_modes, + .skip_non_native = skip_non_native, + .skip_windows = skip_windows, + .skip_llvm = skip_llvm, + .max_rss = 100_000_000, + })); test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native)); test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimize_modes, skip_non_native)); test_step.dependOn(tests.addCliTests(b)); diff --git a/test/link.zig b/test/link.zig new file mode 100644 index 0000000000000000000000000000000000000000..2a5cf4ad44496efe58845842e5883c36fdb6b218 --- /dev/null +++ b/test/link.zig @@ -0,0 +1,17 @@ +pub fn addCases(cases: @import("tests.zig").LinkContext) void { + if (cases.addTestStep("static-lib-exports")) |name| { + const lib = cases.addStaticLibrary(.{ + .name = "lib", + .zig_source_bytes = + \\export fn foo() void {} + \\var bar: u32 = 1234; + \\comptime { @export(&bar, .{ .name = "bar", .linkage = .strong }); } + \\const baz: u64 = 5678; + \\comptime { @export(&baz, .{ .name = "baz", .linkage = .strong }); } + , + }); + cases.verifyObjdump(name, lib, &.{"--symbols"}, .{ .os = true }); + } +} + +const std = @import("std"); diff --git a/test/src/Link.zig b/test/src/Link.zig new file mode 100644 index 0000000000000000000000000000000000000000..ce41727601798f25f39efa5a49bd5aa634a3a729 --- /dev/null +++ b/test/src/Link.zig @@ -0,0 +1,159 @@ +b: *Build, +step: *Step, +optimize: std.builtin.OptimizeMode, +target: std.Build.ResolvedTarget, +use_llvm: bool, +use_lld: bool, +link_libc: bool, +suffix: []const u8, +test_filters: []const []const u8, +max_rss: usize, + +pub fn addTestStep(self: *const Link, prefix: []const u8) ?[]const u8 { + if (for (self.test_filters) |filter| { + if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false; + } else self.test_filters.len > 0) return null; + + return std.fmt.allocPrint(self.b.allocator, "test-{s}", .{prefix}) catch @panic("OOM"); +} + +pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compile { + return self.b.addLibrary(.{ + .linkage = .static, + .name = overlay.name, + .root_module = self.createModule(overlay), + .use_llvm = self.use_llvm, + .use_lld = self.use_lld, + }); +} + +// TODO: Use std.meta.FieldEnum on TargetQuery? +const SnapshotScope = packed struct { + arch: bool = false, + os: bool = false, + abi: bool = false, + optimize: bool = false, + use_llvm: bool = false, + use_lld: bool = false, + link_libc: bool = false, +}; + +pub fn verifyObjdump( + self: *const Link, + name: []const u8, + compile: *Step.Compile, + args: []const []const u8, + scope: SnapshotScope, +) void { + const snapshot_name = self.snapshotName(name, compile.name, scope) catch @panic("OOM"); + const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name})); + run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" }); + run_step.addArtifactArg(compile); + run_step.addArgs(args); + run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); + + const actual_path = run_step.captureStdOut(.{ .trim_whitespace = .none }); + const expected_path = self.b.path(self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name })); + + const check_step = self.b.addCheckFile(actual_path, .{ + .expected_file = .{ + .file = expected_path, + .if_missing = .fail, + // TODO: Option to do UpdateSourceFiles if not matching / missing? + // TODO: Option to output to -.actual.dmp file? + }, + }); + + self.step.dependOn(&check_step.step); +} + +fn snapshotName( + self: *const Link, + test_name: []const u8, + compile_name: []const u8, + scope: SnapshotScope, +) ![]const u8 { + var snapshot_name: std.Io.Writer.Allocating = .init(self.b.allocator); + const w = &snapshot_name.writer; + + try w.print("{s}.{s}", .{ test_name, compile_name }); + if (scope.arch) try w.print("-{t}", .{self.target.result.cpu.arch}); + if (scope.os) try w.print("-{t}", .{self.target.result.os.tag}); + if (scope.abi) try w.print("-{t}", .{self.target.result.abi}); + if (scope.optimize) try w.print("-{t}", .{self.optimize}); + if (scope.use_llvm and self.use_llvm) try w.writeAll("-llvm"); + if (scope.use_lld and self.use_lld) try w.writeAll("-lld"); + if (scope.link_libc and self.link_libc) try w.writeAll("-libc"); + try w.writeAll(".dmp"); + + return try snapshot_name.toOwnedSlice(); +} + +fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { + const write_files = self.b.addWriteFiles(); + + const mod = self.b.createModule(.{ + .target = self.target, + .optimize = self.optimize, + .root_source_file = rsf: { + const bytes = overlay.zig_source_bytes orelse break :rsf null; + const name = self.b.fmt("{s}.zig", .{overlay.name}); + break :rsf write_files.add(name, bytes); + }, + .link_libc = self.link_libc, // TODO: Should this be in overlay instead? + .pic = overlay.pic, + .strip = overlay.strip, + }); + + if (overlay.objcpp_source_bytes) |bytes| { + mod.addCSourceFile(.{ + .file = write_files.add("a.mm", bytes), + .flags = overlay.objcpp_source_flags, + }); + } + if (overlay.objc_source_bytes) |bytes| { + mod.addCSourceFile(.{ + .file = write_files.add("a.m", bytes), + .flags = overlay.objc_source_flags, + }); + } + if (overlay.cpp_source_bytes) |bytes| { + mod.addCSourceFile(.{ + .file = write_files.add("a.cpp", bytes), + .flags = overlay.cpp_source_flags, + }); + } + if (overlay.c_source_bytes) |bytes| { + mod.addCSourceFile(.{ + .file = write_files.add("a.c", bytes), + .flags = overlay.c_source_flags, + }); + } + if (overlay.asm_source_bytes) |bytes| { + mod.addAssemblyFile(write_files.add("a.s", bytes)); + } + + return mod; +} + +const OverlayOptions = struct { + name: []const u8, + asm_source_bytes: ?[]const u8 = null, + c_source_bytes: ?[]const u8 = null, + c_source_flags: []const []const u8 = &.{}, + cpp_source_bytes: ?[]const u8 = null, + cpp_source_flags: []const []const u8 = &.{}, + objc_source_bytes: ?[]const u8 = null, + objc_source_flags: []const []const u8 = &.{}, + objcpp_source_bytes: ?[]const u8 = null, + objcpp_source_flags: []const []const u8 = &.{}, + zig_source_bytes: ?[]const u8 = null, + pic: ?bool = null, + strip: ?bool = null, +}; + +const std = @import("std"); +const Build = std.Build; +const Step = Build.Step; + +const Link = @This(); diff --git a/test/tests.zig b/test/tests.zig index c61f452e53865f17def686de3b3acc39a21a15fb..625fe48c22baf5ce299f0c79a7f9ced3fa7ebe30 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -10,6 +10,7 @@ const error_traces = @import("error_traces.zig"); const stack_traces = @import("stack_traces.zig"); const llvm_ir = @import("llvm_ir.zig"); const libc = @import("libc.zig"); +const link = @import("link.zig"); // Implementations pub const ErrorTracesContext = @import("src/ErrorTrace.zig"); @@ -17,6 +18,7 @@ pub const StackTracesContext = @import("src/StackTrace.zig"); pub const DebuggerContext = @import("src/Debugger.zig"); pub const LlvmIrContext = @import("src/LlvmIr.zig"); pub const LibcContext = @import("src/Libc.zig"); +pub const LinkContext = @import("src/Link.zig"); const ModuleTestTarget = struct { linkage: ?std.builtin.LinkMode = null, @@ -2059,6 +2061,57 @@ const c_abi_targets = blk: { }; }; +const LinkTarget = struct { + target: std.Target.Query = .{}, + link_libc: bool = false, + use_llvm: bool = false, + use_lld: bool = false, +}; + +const link_targets = blk: { + @setEvalBranchQuota(30000); + break :blk [_]LinkTarget{ + // Native Targets + + // .{ + // .use_llvm = true, + // }, + + // Windows Targets + + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + .link_libc = true, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .msvc, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .msvc, + }, + .link_libc = true, + }, + }; +}; + /// Unlike `test_targets` and `c_abi_targets`, these targets are just simple strings which we pass /// directly to `incr-check`. They include the target triple and the compiler backend. /// @@ -3083,6 +3136,61 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step { return step; } +const LinkTestOptions = struct { + test_target_filters: []const []const u8, + test_filters: []const []const u8, + optimize_modes: []const OptimizeMode, + skip_non_native: bool, + skip_windows: bool, + skip_llvm: bool, + max_rss: usize, +}; + +pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step { + const step = b.step("test-link", "Run the linker tests"); + + for (link_targets) |link_target| { + if (options.skip_non_native and !link_target.target.isNative()) continue; + if (options.skip_windows and link_target.target.os_tag == .windows) continue; + + const resolved_target = b.resolveTargetQuery(link_target.target); + const triple_txt = resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"); + const target = &resolved_target.result; + + if (options.test_target_filters.len > 0) { + for (options.test_target_filters) |filter| { + if (std.mem.indexOf(u8, triple_txt, filter) != null) break; + } else continue; + } + + for (options.optimize_modes) |optimize_mode| { + const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode); + if (options.skip_llvm and would_use_llvm) continue; + if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue; + + link.addCases(.{ + .b = b, + .step = step, + .optimize = optimize_mode, + .target = resolved_target, + .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{ + target.zigTriple(b.allocator) catch @panic("OOM"), + optimize_mode, + if (link_target.use_llvm) "-llvm" else "", + if (link_target.use_lld) "-lld" else "", + if (link_target.link_libc) "-libc" else "", + }) catch @panic("OOM"), + .use_llvm = link_target.use_llvm, + .use_lld = link_target.use_lld, + .link_libc = link_target.link_libc, + .test_filters = options.test_filters, + .max_rss = options.max_rss, + }); + } + } + return step; +} + pub fn addCases( b: *std.Build, parent_step: *Step, -- 2.54.0 From 1c6d19b0ff6c816cf9f73507e7e4ff35b19a1e1d Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 61/94] tests: more work on the linker snapshot testing framework build: add the ability to test the output of a Run step against a snapshot objdump: add --redact, --omit-element, --snapshot, and --only-symbol --- lib/compiler/Maker/Step/Run.zig | 49 +- lib/compiler/configurer.zig | 8 + lib/compiler/objdump.zig | 743 ++++++++++++-------- lib/std/Build/Configuration.zig | 6 +- lib/std/Build/Step/Run.zig | 9 + test/link.zig | 35 +- test/link/exports.zig | 9 + test/link/snapshots/exports-dynamic.lib.dmp | 14 + test/link/snapshots/exports-static.lib.dmp | 3 + test/src/Link.zig | 67 +- test/tests.zig | 33 +- 11 files changed, 633 insertions(+), 343 deletions(-) create mode 100644 test/link/exports.zig create mode 100644 test/link/snapshots/exports-dynamic.lib.dmp create mode 100644 test/link/snapshots/exports-static.lib.dmp diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index 8b30753ecacc8b25c016286f6b110c13ed6f318c..af4819f62b2717a528ee3823aad1fee269f115c8 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -2100,6 +2100,47 @@ fn runCommand( }); } } + const snapshots: []const ?struct { + path: Cache.Path, + result: enum { stderr, stdout }, + } = &.{ + if (conf_run.expect_stderr_snapshot.value) |path| .{ + .path = try maker.resolveLazyPathIndex(arena, path, run_index), + .result = .stderr, + } else null, + if (conf_run.expect_stdout_snapshot.value) |path| .{ + .path = try maker.resolveLazyPathIndex(arena, path, run_index), + .result = .stdout, + } else null, + }; + for (snapshots) |opt_snapshot| { + const snapshot = opt_snapshot orelse continue; + + const file = snapshot.path.root_dir.handle.openFile(io, snapshot.path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open snapshot file {f}: {t}", .{ snapshot.path, err }); + defer file.close(io); + + var file_reader = file.reader(io, &.{}); + const snapshot_contents = file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| + return step.fail(maker, "unable to read snapshot file {f}: {t}", .{ snapshot.path, err }); + defer gpa.free(snapshot_contents); + + const result = switch (snapshot.result) { + .stdout => generic_result.stdout.?, + .stderr => generic_result.stderr.?, + }; + if (!mem.eql(u8, snapshot_contents, result)) { + return step.fail(maker, + \\ + \\========= snapshot file: ========= + \\{f} + \\========= contained: ============= + \\{s} + \\========= {t} output was: ======== + \\{s} + , .{ snapshot.path, snapshot_contents, snapshot.result, result }); + } + } }, else => { // On failure, report captured stderr like normal standard error output. @@ -2283,11 +2324,15 @@ fn setColorEnvironmentVariables( } fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool { - return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0; + return conf_run.expect_stdout_exact.value != null or + conf_run.expect_stdout_match.slice.len != 0 or + conf_run.expect_stdout_snapshot.value != null; } fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool { - return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0; + return conf_run.expect_stderr_exact.value != null or + conf_run.expect_stderr_match.slice.len != 0 or + conf_run.expect_stderr_snapshot.value != null; } /// If `path` is cwd-relative, make it relative to the cwd of the child instead. diff --git a/lib/compiler/configurer.zig b/lib/compiler/configurer.zig index 58268882582d1aa121ec0df31433d5aa4f1f1768..eef7b4dfdfba367bb7be85998da50f080a42217b 100644 --- a/lib/compiler/configurer.zig +++ b/lib/compiler/configurer.zig @@ -1006,6 +1006,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { status: Configuration.Step.Run.ExpectTermStatus, value: u32, } = null; + var expect_stderr_snapshot: ?Configuration.LazyPath.Index = null; + var expect_stdout_snapshot: ?Configuration.LazyPath.Index = null; switch (run.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |bytes| expect_stderr_exact = try wc.addBytes(bytes), @@ -1022,6 +1024,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) }, .unknown => |x| .{ .status = .unknown, .value = x }, }, + .expect_stderr_snapshot => |path| expect_stderr_snapshot = try s.addLazyPath(path), + .expect_stdout_snapshot => |path| expect_stdout_snapshot = try s.addLazyPath(path), }, else => {}, } @@ -1061,6 +1065,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .expect_stdout_match = expect_stdout_match.items.len != 0, .expect_term = expect_term != null, .expect_term_status = if (expect_term) |t| t.status else .exited, + .expect_stderr_snapshot = expect_stderr_snapshot != null, + .expect_stdout_snapshot = expect_stdout_snapshot != null, }, .file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) }, .args = .{ .slice = try s.initArgsList(run.argv.items) }, @@ -1081,6 +1087,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null }, .expect_stderr_match = .{ .slice = expect_stderr_match.items }, .expect_stdout_match = .{ .slice = expect_stdout_match.items }, + .expect_stderr_snapshot = .{ .value = expect_stderr_snapshot orelse null }, + .expect_stdout_snapshot = .{ .value = expect_stdout_snapshot orelse null }, .stdin = .{ .u = switch (run.stdin) { .none => .none, .bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) }, diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index ddb88fa104d7f88754e97718e77f9325718d7eb4..69e19e2ac38ce836a6e7689e898418b63e53763d 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -16,9 +16,12 @@ const Options = struct { input_path: []const u8, member_filters: []const []const u8 = &.{}, member_headers: bool, + omit_elements: std.enums.EnumArray(Element, bool), + redact: std.enums.EnumArray(FieldKind, bool), relocs: bool, section_filters: []const []const u8 = &.{}, section_headers: bool, + symbol_filters: []const []const u8 = &.{}, strings: bool, symbols: bool, tls: bool, @@ -27,6 +30,20 @@ const Options = struct { linker_member: ?std.coff.ArchiveMemberHeader.Kind, }; +const FieldKind = enum { + va, + rva, + ord, + size, +}; + +const Element = enum { + @"file-type", + @"table-header", + @"header-names", + newlines, +}; + pub fn main(init: std.process.Init) !void { const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); @@ -40,12 +57,15 @@ pub fn main(init: std.process.Init) !void { var opt_input_path: ?[]const u8 = null; var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null; var opt_member_headers: ?bool = null; + var omit_elements: @FieldType(Options, "omit_elements") = .initFill(false); + var redact: @FieldType(Options, "redact") = .initFill(false); var opt_relocs: ?bool = null; var opt_section_headers: ?bool = null; var opt_strings: ?bool = null; var opt_symbols: ?bool = null; var opt_tls: ?bool = null; var section_filters: std.ArrayList([]const u8) = .empty; + var symbol_filters: std.ArrayList([]const u8) = .empty; var member_filters: std.ArrayList([]const u8) = .empty; while (i < args.len) : (i += 1) { const arg = args[i]; @@ -73,14 +93,37 @@ pub fn main(init: std.process.Init) !void { opt_linker_member = .second_linker; } else if (mem.eql(u8, arg, "--member-headers")) { opt_member_headers = true; - } else if (mem.startsWith(u8, arg, "--only-section=")) { - (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); + } else if (mem.startsWith(u8, arg, "--omit-element=")) { + const kind = arg["--omit-element=".len..]; + if (std.meta.stringToEnum(Element, kind)) |format_kind| { + omit_elements.set(format_kind, true); + } else if (std.mem.eql(u8, kind, "all")) { + omit_elements = .initFill(true); + } else { + fatal("unrecognized element: {s}", .{kind}); + } } else if (mem.startsWith(u8, arg, "--only-member=")) { (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]); + } else if (mem.startsWith(u8, arg, "--only-section=")) { + (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); + } else if (mem.startsWith(u8, arg, "--only-symbol=")) { + (try symbol_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-symbol=".len..]); + } else if (mem.startsWith(u8, arg, "--redact=")) { + const kind = arg["--redact=".len..]; + if (std.meta.stringToEnum(FieldKind, kind)) |field_kind| { + redact.set(field_kind, true); + } else if (std.mem.eql(u8, kind, "all")) { + redact = .initFill(true); + } else { + fatal("unrecognized redaction kind: {s}", .{kind}); + } } else if (mem.eql(u8, arg, "--relocs")) { opt_relocs = true; } else if (mem.eql(u8, arg, "--section-headers")) { opt_section_headers = true; + } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--snapshot")) { + omit_elements = .initFill(true); + redact = .initFill(true); } else if (mem.eql(u8, arg, "--strings")) { opt_strings = true; } else if (mem.eql(u8, arg, "--symbols")) { @@ -105,10 +148,13 @@ pub fn main(init: std.process.Init) !void { .linker_member = opt_linker_member, .member_filters = member_filters.items, .member_headers = opt_member_headers orelse false, + .omit_elements = omit_elements, + .redact = redact, + .relocs = opt_relocs orelse false, .section_filters = section_filters.items, .section_headers = opt_section_headers orelse false, - .relocs = opt_relocs orelse false, .strings = opt_strings orelse false, + .symbol_filters = symbol_filters.items, .symbols = opt_symbols orelse false, .tls = opt_tls orelse false, }; @@ -120,7 +166,15 @@ pub fn main(init: std.process.Init) !void { var buffer: [4096]u8 = undefined; var file_reader = file.reader(io, &buffer); var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); - dump(init.gpa, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) { + + const ctx: DumpContext = .{ + .gpa = init.gpa, + .opts = &opts, + .fr = &file_reader, + .w = &stdout_writer.interface, + }; + + dump(&ctx) catch |err| switch (err) { error.ReadFailed => return file_reader.err.?, error.WriteFailed => return stdout_writer.err.?, error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), @@ -130,60 +184,82 @@ pub fn main(init: std.process.Init) !void { try stdout_writer.flush(); } -fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { - const r = &fr.interface; +fn dump(d: *const DumpContext) !void { + const r = &d.fr.interface; try r.fill(4); elf: { if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf; - return elf.dump(r, w); + return elf.dump(r, d.w); } macho: { if (mem.readInt(u32, r.buffered()[0..4], .little) != std.macho.MH_MAGIC_64) break :macho; - return macho.dump(r, w); + return macho.dump(r, d.w); } wasm: { comptime assert(std.wasm.magic.len == 4); if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm; - return wasm.dump(r, w); + return wasm.dump(r, d.w); } coff: { - const ext = std.fs.path.extension(opts.input_path); - const basename = std.fs.path.basename(opts.input_path); + const ext = std.fs.path.extension(d.opts.input_path); + const basename = std.fs.path.basename(d.opts.input_path); if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) { if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff; try r.discardAll(std.coff.pe_pointer_offset); const sig_offset = try r.takeInt(u32, .little); - try fr.seekTo(sig_offset); + try d.fr.seekTo(sig_offset); const sig = try r.take(4); if (!std.mem.eql(u8, sig, std.coff.pe_signature)) { - try w.print("invalid PE signature: {x}", .{sig}); + try d.w.print("invalid PE signature: {x}", .{sig}); return error.ParseFailure; } - try w.print("{s}: PE/COFF image\n\n", .{basename}); - return coff.dumpObject(gpa, opts, true, basename, fr, w); + if (d.element(.@"file-type")) + try d.w.print("{s}: PE/COFF image\n\n", .{basename}); + + return coff.dumpObject(d, true, basename); } else if (std.mem.eql(u8, ext, ".lib")) { r.fill(std.coff.archive_signature.len) catch break :coff; if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; - try w.print("{s}: COFF archive\n\n", .{basename}); - return coff.dumpArchive(gpa, opts, fr, w); + if (d.element(.@"file-type")) + try d.w.print("{s}: COFF archive\n\n", .{basename}); + + return coff.dumpArchive(d); } else if (std.mem.eql(u8, ext, ".obj")) { - try w.print("{s}: COFF object\n\n", .{basename}); - return coff.dumpObject(gpa, opts, false, basename, fr, w); + if (d.element(.@"file-type")) + try d.w.print("{s}: COFF object\n\n", .{basename}); + + return coff.dumpObject(d, false, basename); } } return error.UnknownFile; } -fn failParse( +const DumpContext = struct { + gpa: std.mem.Allocator, opts: *const Options, - comptime fmt: []const u8, - args: anytype, -) noreturn { - std.log.err("error parsing '{s}'", .{std.fs.path.basename(opts.input_path)}); - fatal(fmt, args); -} + fr: *Io.File.Reader, + w: *Io.Writer, + + fn element(self: *const DumpContext, e: Element) bool { + return !self.opts.omit_elements.get(e); + } + + fn redacted(self: *const DumpContext, opt_kind: ?FieldKind) bool { + const kind = opt_kind orelse return false; + return self.opts.redact.get(kind); + } + + fn failParse( + ctx: *const DumpContext, + comptime fmt: []const u8, + args: anytype, + ) noreturn { + std.log.err("error parsing '{s}'", .{std.fs.path.basename(ctx.opts.input_path)}); + fatal(fmt, args); + } +}; const elf = struct { fn dump(r: *Io.Reader, w: *Io.Writer) !void { @@ -230,29 +306,33 @@ const coff = struct { file_mode: u24, size: u34, - pub fn fromRaw(opts: *const Options, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() { + pub fn fromRaw(d: *const DumpContext, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() { const name = raw_header.parseName(opt_longnames) catch |err| switch (err) { - error.BadName => failParse(opts, "malformed member name: '{s}'", .{&raw_header.name}), - error.NoLongNames => failParse(opts, "member uses a long name, but there was no longnames member", .{}), + error.BadName => d.failParse("malformed member name: '{s}'", .{&raw_header.name}), + error.NoLongNames => d.failParse("member uses a long name, but there was no longnames member", .{}), }; return .{ .name = name, .date = raw_header.parseDate() catch |err| - failParse(opts, "unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }), + d.failParse("unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }), .user_id = raw_header.parseUserId() catch |err| - failParse(opts, "unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }), + d.failParse("unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }), .group_id = raw_header.parseGroupId() catch |err| - failParse(opts, "unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }), + d.failParse("unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }), .file_mode = raw_header.parseFileMode() catch |err| - failParse(opts, "unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }), + d.failParse("unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }), .size = raw_header.parseSize() catch |err| - failParse(opts, "unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }), + d.failParse("unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }), }; } }; - fn dumpArchive(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { + fn dumpArchive(d: *const DumpContext) !void { + const gpa = d.gpa; + const fr = d.fr; + const w = d.w; + const r = &fr.interface; r.toss(std.coff.archive_signature.len); @@ -272,26 +352,26 @@ const coff = struct { while (pos < size) : (pos = fr.logicalPos()) { if ((pos & 1) != 0) try r.discardAll(1); const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); - const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames); + const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames); if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header)) - return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header }); + return d.failParse("malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header }); const dump_header = - (opts.member_headers and filterMatches(opts.member_filters, header.name)) or - (opts.linker_member == opt_expected_kind); + (d.opts.member_headers and filterMatches(d.opts.member_filters, header.name)) or + (d.opts.linker_member == opt_expected_kind); if (dump_header) - try dumpArchiveHeader(w, &header, @intCast(pos)); + try dumpArchiveHeader(d, &header, @intCast(pos)); const member_end = fr.logicalPos() + header.size; if (member_end > size) - return failParse(opts, "out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name }); + return d.failParse("out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name }); if (opt_expected_kind) |expected_kind| switch (expected_kind) { .first_linker => { if (!std.mem.eql(u8, header.name, "/")) - return failParse(opts, "expected first linker member, found '{s}'", .{header.name}); + return d.failParse("expected first linker member, found '{s}'", .{header.name}); const num_symbols = try r.takeInt(u32, .big); if (dump_header) @@ -301,7 +381,7 @@ const coff = struct { \\ , .{ expected_kind, num_symbols }); - if (opts.linker_member == .first_linker) { + if (d.opts.linker_member == .first_linker) { try w.writeAll( \\ \\Archive symbols: @@ -314,11 +394,15 @@ const coff = struct { for (0..num_symbols) |symbol_i| { const symbol = r.takeDelimiter(0) catch |err| - return failParse(opts, "unable to read first linker member string table: {t}", .{err}); - try w.print("{x: >8} {s}\n", .{ std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big), symbol.? }); + return d.failParse("unable to read first linker member string table: {t}", .{err}); + const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big); + try w.print("{f} {s}\n", .{ + fmtIntField(d, offset, .{ .kind = .va }), + symbol.?, + }); } } - if (dump_header) try w.writeByte('\n'); + if (dump_header and d.element(.newlines)) try w.writeByte('\n'); try fr.seekTo(member_end); opt_expected_kind = .second_linker; @@ -326,12 +410,12 @@ const coff = struct { }, .second_linker => { if (!std.mem.eql(u8, header.name, "/")) - return failParse(opts, "expected second linker member, found '{s}'", .{header.name}); + return d.failParse("expected second linker member, found '{s}'", .{header.name}); const num_members = try r.takeInt(u32, .little); pos = fr.logicalPos(); if (pos + num_members * @sizeOf(u32) > member_end) - return failParse(opts, "invalid member count 0x{x} in second linker member", .{num_members}); + return d.failParse("invalid member count 0x{x} in second linker member", .{num_members}); try members.ensureTotalCapacity(gpa, num_members); for (0..num_members) |_| @@ -342,7 +426,7 @@ const coff = struct { const num_symbols = try r.takeInt(u32, .little); pos = fr.logicalPos(); if (pos + num_symbols * @sizeOf(u16) > member_end) - return failParse(opts, "invalid symbol count 0x{x} in second linker member", .{num_symbols}); + return d.failParse("invalid symbol count 0x{x} in second linker member", .{num_symbols}); if (dump_header) try w.print( @@ -356,13 +440,14 @@ const coff = struct { for (0..num_symbols) |_| symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1; - if (opts.linker_member == .second_linker) { - try w.writeAll( - \\ - \\Archive Symbols: - \\& Member Symbol - \\ - ); + if (d.opts.linker_member == .second_linker) { + if (d.element(.@"table-header")) + try w.writeAll( + \\ + \\Archive Symbols: + \\& Member Symbol + \\ + ); pos = fr.logicalPos(); var symbol_i: u32 = 0; @@ -373,23 +458,26 @@ const coff = struct { const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) { error.StreamTooLong => null, else => |e| return e, - }) |n| n else return failParse(opts, "unterminated string found in second linker member", .{}); + }) |n| n else return d.failParse("unterminated string found in second linker member", .{}); - try w.print("{x: >8} {s}\n", .{ - members.items[symbol_member_indices.items[symbol_i]].offset, + try w.print("{f} {s}\n", .{ + fmtIntField( + d, + members.items[symbol_member_indices.items[symbol_i]].offset, + .{ .kind = .va }, + ), symbol_name, }); } if (symbol_i != num_symbols) - return failParse( - opts, + return d.failParse( " expected {d} entries in second linker member string table, but found {d}", .{ num_symbols, symbol_i }, ); } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); try fr.seekTo(member_end); opt_expected_kind = .longnames; continue; @@ -401,12 +489,13 @@ const coff = struct { if (dump_header) try w.print("{t: >16} type\n", .{expected_kind}); - if (opts.linker_member == .longnames) { - try w.print( - \\ - \\Longnames (0x{x} bytes): - \\ - , .{opt_longnames.?.len}); + if (d.opts.linker_member == .longnames) { + if (d.element(.@"table-header")) + try w.print( + \\ + \\Longnames (0x{x} bytes): + \\ + , .{opt_longnames.?.len}); var lr = Io.Reader.fixed(opt_longnames.?); while (try lr.takeDelimiter(0)) |str| { @@ -415,7 +504,7 @@ const coff = struct { } } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); } opt_expected_kind = null; @@ -426,18 +515,18 @@ const coff = struct { } if (opt_expected_kind) |expected_kind| switch (expected_kind) { - .first_linker => failParse(opts, "missing first linker member", .{}), - .second_linker => failParse(opts, "missing second linker member", .{}), + .first_linker => d.failParse("missing first linker member", .{}), + .second_linker => d.failParse("missing second linker member", .{}), else => {}, }; for (members.items, 0..) |member, member_i| { fr.seekTo(member.offset) catch |err| - failParse(opts, "unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err }); + d.failParse("unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err }); const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); - const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames); - if (!filterMatches(opts.member_filters, header.name)) continue; + const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames); + if (!filterMatches(d.opts.member_filters, header.name)) continue; const member_sig = try r.peek(4); const machine: std.coff.IMAGE.FILE.MACHINE = @@ -445,17 +534,17 @@ const coff = struct { const sig = std.mem.readInt(u16, member_sig[2..4], .little); const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; - if (opts.member_headers or (opts.exports and is_imp_lib)) { - try dumpArchiveHeader(w, &header, member.offset); + if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) { + try dumpArchiveHeader(d, &header, member.offset); if (is_imp_lib) { try w.writeAll("\nImport header:\n"); const imp_header = try r.takeStruct(std.coff.ImportHeader, .little); - try dumpHeader(w, std.coff.ImportHeader, &imp_header, struct { - pub fn sig1(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {} - pub fn sig2(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {} - pub fn types(h: *const std.coff.ImportHeader, cw: *Io.Writer) !void { - try cw.print( + try dumpHeader(d, std.coff.ImportHeader, &imp_header, struct { + pub fn sig1(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {} + pub fn sig2(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {} + pub fn types(id: *const DumpContext, h: *const std.coff.ImportHeader) !void { + try id.w.print( \\{t: >16} import_type \\{t: >16} name_type \\ @@ -490,51 +579,53 @@ const coff = struct { } else { try w.writeAll(" COFF object type\n"); } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); } if (is_imp_lib) continue; - if (opts.section_headers or - opts.file_headers or - opts.relocs or - opts.strings or - opts.symbols) + if (d.opts.section_headers or + d.opts.file_headers or + d.opts.relocs or + d.opts.strings or + d.opts.symbols) { - try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(opts.input_path), header.name }); - try dumpObject(gpa, opts, false, header.name, fr, w); + if (d.element(.@"file-type")) + try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(d.opts.input_path), header.name }); + try dumpObject(d, false, header.name); } } } fn dumpObject( - gpa: std.mem.Allocator, - opts: *const Options, + d: *const DumpContext, is_image: bool, obj_name: []const u8, - fr: *Io.File.Reader, - w: *Io.Writer, ) !void { + const gpa = d.gpa; + const fr = d.fr; + const w = d.w; + const file_location = fr.logicalPos(); const r = &fr.interface; const header = r.takeStruct(std.coff.Header, .little) catch |err| - return failParse(opts, "unable to read COFF header: {t}", .{err}); + return d.failParse("unable to read COFF header: {t}", .{err}); - if (opts.file_headers) { - try w.writeAll("COFF Header:\n"); - try dumpHeader(w, std.coff.Header, &header, struct {}); - try w.writeByte('\n'); + if (d.opts.file_headers) { + if (d.element(.@"header-names")) try w.writeAll("COFF Header:\n"); + try dumpHeader(d, std.coff.Header, &header, struct {}); + if (d.element(.newlines)) try w.writeByte('\n'); } switch (header.machine) { - _ => return failParse(opts, "unknown machine type: {x}", .{header.machine}), + _ => return d.failParse("unknown machine type: {x}", .{header.machine}), else => {}, } var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined; const needs_data_dirs = - opts.exports or - opts.imports or - opts.tls; + d.opts.exports or + d.opts.imports or + d.opts.tls; const ImageInfo = struct { data_dirs: []const std.coff.ImageDataDirectory, @@ -543,12 +634,14 @@ const coff = struct { }; const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: { - if (!opts.file_headers and !needs_data_dirs) { + if (!d.opts.file_headers and !needs_data_dirs) { try fr.seekBy(header.size_of_optional_header); break :image_info null; } - if (opts.file_headers) try w.writeAll("COFF Optional Header:\n"); + if (d.opts.file_headers and d.element(.@"header-names")) + try w.writeAll("COFF Optional Header:\n"); + const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); const num_directory_entries, const image_base = switch (magic) { inline .PE32, .@"PE32+" => |v| num_data_dirs: { @@ -558,46 +651,46 @@ const coff = struct { std.coff.OptionalHeader.@"PE32+"; const optional_header = r.takeStruct(OptionalHeader, .little) catch |err| - return failParse(opts, "unable to read optional header: {t}", .{err}); + return d.failParse("unable to read optional header: {t}", .{err}); - if (opts.file_headers) { - try dumpHeader(w, OptionalHeader, &optional_header, struct { - pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + if (d.opts.file_headers) { + try dumpHeader(d, OptionalHeader, &optional_header, struct { + pub fn base_of_code(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void { const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; - try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); + try dumpRvaField(id, @src().fn_name, h.base_of_code, base); } - pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { + pub fn address_of_entry_point(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void { const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; - try dumpRvaField(cw, @src().fn_name, h.base_of_code, base); + try dumpRvaField(id, @src().fn_name, h.base_of_code, base); } - pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version); + pub fn major_linker_version(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void { + try dumpVersionField(id.w, "linker_version", h.major_linker_version, h.minor_linker_version); } - pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {} + pub fn minor_linker_version(_: *const DumpContext, _: *const std.coff.OptionalHeader) !void {} - pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void { + pub fn major_operating_system_version(id: *const DumpContext, h: *const OptionalHeader) !void { try dumpVersionField( - cw, + id.w, "operating_system_version", h.major_operating_system_version, h.minor_operating_system_version, ); } - pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + pub fn minor_operating_system_version(_: *const DumpContext, _: *const OptionalHeader) !void {} - pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version); + pub fn major_image_version(id: *const DumpContext, h: *const OptionalHeader) !void { + try dumpVersionField(id.w, "image_version", h.major_image_version, h.minor_image_version); } - pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + pub fn minor_image_version(_: *const DumpContext, _: *const OptionalHeader) !void {} - pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void { - try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version); + pub fn major_subsystem_version(id: *const DumpContext, h: *const OptionalHeader) !void { + try dumpVersionField(id.w, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version); } - pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} + pub fn minor_subsystem_version(_: *const DumpContext, _: *const OptionalHeader) !void {} }); - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); } break :num_data_dirs .{ @@ -605,24 +698,26 @@ const coff = struct { optional_header.image_base, }; }, - else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), + else => return d.failParse("invalid optional header magic number: {x}", .{magic}), }; - if (opts.file_headers) try w.writeAll("Data Directories:\n"); + if (d.opts.file_headers and d.element(.@"header-names")) + try w.writeAll("Data Directories:\n"); + for (0..num_directory_entries) |dir_i| { const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| - return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); + return d.failParse("unable to read data directory {x}: {t}", .{ dir_i, err }); if (dir_i < known_dirs.len) known_dirs[dir_i] = dir; - if (opts.file_headers) + if (d.opts.file_headers) try w.print( "{x: >16} {x: >8} {t}\n", .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, ); } - if (opts.file_headers) try w.writeByte('\n'); + if (d.opts.file_headers and d.element(.newlines)) try w.writeByte('\n'); break :image_info .{ .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)], @@ -630,32 +725,33 @@ const coff = struct { .image_base = image_base, }; } else if (is_image) { - return failParse(opts, "image did not contain an optional header", .{}); + return d.failParse("image did not contain an optional header", .{}); } else null; // Section names in images don't use the string table, as they must fit inline in the header - const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; + const load_string_table = (d.opts.strings or !is_image) and header.pointer_to_symbol_table > 0; const string_table = if (load_string_table) string_table: { const pos = fr.logicalPos(); fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| - return failParse(opts, "unable to seek to string table: {t}", .{err}); + return d.failParse("unable to seek to string table: {t}", .{err}); const string_table_len = r.peekInt(u32, .little) catch |err| - return failParse(opts, "unable to read string table length: {t}", .{err}); + return d.failParse("unable to read string table length: {t}", .{err}); const table = r.readAlloc(gpa, string_table_len) catch |err| - return failParse(opts, "unable to read string table: {t}", .{err}); + return d.failParse("unable to read string table: {t}", .{err}); try fr.seekTo(pos); break :string_table table; } else &.{}; defer gpa.free(string_table); - if (opts.strings) { - try w.print( - \\String Table (0x{x} bytes): - \\ - , .{string_table.len}); + if (d.opts.strings) { + if (d.element(.@"table-header")) + try w.print( + \\String Table (0x{x} bytes): + \\ + , .{string_table.len}); var sr = Io.Reader.fixed(string_table[4..]); while (try sr.takeDelimiter(0)) |str| { @@ -663,7 +759,7 @@ const coff = struct { try w.writeByte('\n'); } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); } var sections: std.ArrayList(Section) = .empty; @@ -671,13 +767,13 @@ const coff = struct { var sections_with_data: u16 = 0; const load_sections = - opts.section_headers or - opts.symbols or - opts.relocs or + d.opts.section_headers or + d.opts.symbols or + d.opts.relocs or needs_data_dirs; if (load_sections) { - if (opts.section_headers) + if (d.opts.section_headers and d.element(.@"table-header")) try w.print( \\Sections in '{s}': \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags @@ -687,37 +783,37 @@ const coff = struct { try sections.resize(gpa, header.number_of_sections); for (sections.items, 0..) |*section, section_i| { section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err| - return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err }); + return d.failParse("unable to read section header {x}: {t}", .{ section_i, err }); section.name = headerName(§ion.header.name, string_table) catch |err| switch (err) { error.Overflow, error.InvalidCharacter, - => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{ + => return d.failParse("unable to parse section name offset '{s}': {t}", .{ section.name, err, }), - error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{ + error.OutOfBounds => return d.failParse("section name offset '{s}' was out of bounds (>= {x})", .{ section.name, string_table.len, }), }; sections_with_data += @intFromBool(section.header.size_of_raw_data > 0); - if (opts.section_headers) { - if (!filterMatches(opts.section_filters, section.name)) continue; + if (d.opts.section_headers) { + if (!filterMatches(d.opts.section_filters, section.name)) continue; const raw_name = std.mem.sliceTo(§ion.header.name, 0); try w.print( - "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} |", + "{x: >3} {s: <8} {f} {f} {f} {f} {f} {f} {f} {f} {x:0>8} |", .{ section_i + 1, raw_name, - section.header.virtual_address, - section.header.virtual_size, - section.header.size_of_raw_data, - section.header.pointer_to_raw_data, - section.header.pointer_to_relocations, - section.header.pointer_to_linenumbers, - section.header.number_of_relocations, - section.header.number_of_linenumbers, + fmtIntField(d, section.header.virtual_address, .{ .kind = .va }), + fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = 9 }), + fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = 9 }), + fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }), + fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }), + fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }), + fmtIntField(d, section.header.number_of_relocations, .{ .kind = .va }), + fmtIntField(d, section.header.number_of_linenumbers, .{ .kind = .va }), @as(u32, @bitCast(section.header.flags)), }, ); @@ -730,7 +826,7 @@ const coff = struct { } } - if (opts.section_headers) try w.writeByte('\n'); + if (d.opts.section_headers and d.element(.newlines)) try w.writeByte('\n'); } var symbols: std.ArrayList(struct { @@ -738,15 +834,15 @@ const coff = struct { section_number: std.coff.SectionNumber, }) = .empty; defer symbols.deinit(gpa); - if (opts.relocs) + if (d.opts.relocs) try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); - if (opts.symbols or opts.relocs) { + if (d.opts.symbols or d.opts.relocs) { if (header.pointer_to_symbol_table > 0) { fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err| - return failParse(opts, "unable to seek to symbol table: {t}", .{err}); + return d.failParse("unable to seek to symbol table: {t}", .{err}); - if (opts.symbols) + if (d.opts.symbols and d.element(.@"table-header")) try w.print( \\Symbols in '{s}': \\ Ord Value Sect Type Storage Name @@ -758,7 +854,7 @@ const coff = struct { while (symbol_i < header.number_of_symbols) { var symbol: std.coff.Symbol = undefined; const symbol_bytes = r.take(symbol_size) catch |err| - return failParse(opts, "unable to read symbol {x}: {t}", .{ symbol_i, err }); + return d.failParse("unable to read symbol {x}: {t}", .{ symbol_i, err }); @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes); if (native_endian != .little) @@ -773,7 +869,7 @@ const coff = struct { const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], .little); if (index >= string_table.len) - return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{ + return d.failParse("invalid name offset for symbol {x} ({x} >= {x})", .{ symbol_i, index, string_table.len, @@ -781,16 +877,19 @@ const coff = struct { break :name string_table[index..]; } else &symbol.name, 0); - if (opts.relocs) + if (d.opts.relocs) symbols.appendNTimesAssumeCapacity(.{ .name = name, .section_number = symbol.section_number, }, 1 + symbol.number_of_aux_symbols); - if (!opts.symbols) + if (!d.opts.symbols or !filterMatches(d.opts.symbol_filters, name)) continue; - try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value }); + try w.print("{f} {x:0>8} ", .{ + fmtIntField(d, @as(u16, @intCast(symbol_i)), .{ .kind = .ord }), + symbol.value, + }); try switch (symbol.section_number) { .UNDEFINED => w.writeAll("UNDEF"), .ABSOLUTE => w.writeAll(" ABS"), @@ -814,8 +913,7 @@ const coff = struct { else => null, }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type}); - try w.print("{t: >16} | {s}", .{ symbol.storage_class, name }); - try w.writeByte('\n'); + try w.print("{t: >16} | {s}\n", .{ symbol.storage_class, name }); for (0..symbol.number_of_aux_symbols) |aux_i| { _ = aux_i; @@ -838,8 +936,7 @@ const coff = struct { try w.writeAll("TODO bf / ef aux symbol"); } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) { if (symbol.value != 0) - return failParse( - opts, + return d.failParse( "invalid value 0x{x} for weak external symbol 0x{x}", .{ symbol.value, symbol_i }, ); @@ -850,14 +947,11 @@ const coff = struct { std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external); if (weak_external.tag_index >= header.number_of_symbols) - return failParse( - opts, + return d.failParse( "invalid tag_index 0x{x} for weak external symbol 0x{x}", .{ weak_external.tag_index, symbol_i }, ); - // TODO - try w.print(" Weak External [falls back to {x:0>8} via {t}]", .{ weak_external.tag_index, weak_external.flag, @@ -914,8 +1008,8 @@ const coff = struct { continue; } - try w.print(" [size {x:0>8} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{ - section_def.length, + try w.print(" [size {f} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{ + fmtIntField(d, section_def.length, .{ .kind = .size, .zero_fill = true }), section_def.checksum, section_def.number_of_relocations, section_def.number_of_linenumbers, @@ -930,19 +1024,19 @@ const coff = struct { try w.writeAll(")"); }, } - } else {} + } try w.writeByte('\n'); } } - if (opts.symbols) try w.writeByte('\n'); - } else if (opts.symbols) { + if (d.opts.symbols and d.element(.newlines)) try w.writeByte('\n'); + } else if (d.opts.symbols) { try w.writeAll("No symbol table found\n"); } } - if (opts.relocs) { + if (d.opts.relocs) { const relocation_size = std.coff.Relocation.sizeOf(); for (sections.items, 0..) |section, section_i| { @@ -955,7 +1049,7 @@ const coff = struct { , .{ section_i + 1, section.name, obj_name }); fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err| - return failParse(opts, "unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err }); + return d.failParse("unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err }); for (0..section.header.number_of_relocations) |reloc_i| { var reloc: std.coff.Relocation = undefined; @@ -963,7 +1057,13 @@ const coff = struct { if (native_endian != .little) std.mem.byteSwapAllFields(std.coff.Relocation, &reloc); - try w.print("{x:0>8} ", .{reloc.virtual_address}); + const sym = &symbols.items[reloc.symbol_table_index]; + if (!filterMatches(d.opts.symbol_filters, sym.name)) + continue; + + try w.print("{f} ", .{ + fmtIntField(d, reloc.virtual_address, .{ .kind = .va, .zero_fill = true }), + }); switch (header.machine) { _ => unreachable, inline else => |m| switch (m.RelocationType()) { @@ -976,20 +1076,18 @@ const coff = struct { } if (reloc.symbol_table_index >= symbols.items.len) - return failParse( - opts, + return d.failParse( "reloc {x} in section {x} has out-of-bounds symbol index {x}", .{ reloc_i, section_i + 1, reloc.symbol_table_index }, ); - const sym = &symbols.items[reloc.symbol_table_index]; - try w.print("{x: >8} {f} | {s}\n", .{ - reloc.symbol_table_index, + try w.print("{f} {f} | {s}\n", .{ + fmtIntField(d, reloc.symbol_table_index, .{ .kind = .ord }), fmtSectionNumber(sym.section_number), sym.name, }); } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); } } @@ -1026,44 +1124,40 @@ const coff = struct { } else &.{}; defer gpa.free(rva_index); - if (opts.exports) { - if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, image_info.?.data_dirs, .EXPORT)) |section_index| { + if (d.opts.exports) { + if (try seekToDataDirectory(d, rva_index, sections.items, image_info.?.data_dirs, .EXPORT)) |section_index| { const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err| - return failParse(opts, "unable to read export directory: {t}", .{err}); + return d.failParse("unable to read export directory: {t}", .{err}); try w.print("Export directory:\n", .{}); - try dumpHeader(w, std.coff.ExportDirectoryTable, &export_dir, struct { - pub fn major_version(h: *const std.coff.ExportDirectoryTable, cw: *Io.Writer) !void { - try dumpVersionField(cw, "version", h.major_version, h.minor_version); + try dumpHeader(d, std.coff.ExportDirectoryTable, &export_dir, struct { + pub fn major_version(id: *const DumpContext, h: *const std.coff.ExportDirectoryTable) !void { + try dumpVersionField(id.w, "version", h.major_version, h.minor_version); } - pub fn minor_version(_: *const std.coff.ExportDirectoryTable, _: *Io.Writer) !void {} + pub fn minor_version(_: *const DumpContext, _: *const std.coff.ExportDirectoryTable) !void {} }); const section = sections.items[section_index]; const name_loc = section.rvaFileOffset(export_dir.name_rva) catch - return failParse( - opts, + return d.failParse( "export name rva 0x{x} was not within the export section", .{export_dir.name_rva}, ); const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch - return failParse( - opts, + return d.failParse( "export address table rva 0x{x} was not within the export section", .{export_dir.export_address_table_rva}, ); const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch - return failParse( - opts, + return d.failParse( "export name pointer table rva 0x{x} was not within the export section", .{export_dir.name_pointer_table_rva}, ); const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch - return failParse( - opts, + return d.failParse( "export ordinal table rva 0x{x} was not within the export section", .{export_dir.ordinal_table_rva}, ); @@ -1077,12 +1171,13 @@ const coff = struct { defer gpa.free(dir_slice); const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0); - try w.print( - \\ - \\Exports from {s}: - \\ Ord Hint RVA Name - \\ - , .{dll_name}); + if (d.element(.@"table-header")) + try w.print( + \\ + \\Exports from {s}: + \\ Ord Hint RVA Name + \\ + , .{dll_name}); const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)]; const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)]; @@ -1090,18 +1185,24 @@ const coff = struct { const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable); for (0..export_dir.number_of_names) |name_i| { const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little); + const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0); + if (!filterMatches(d.opts.symbol_filters, name)) + continue; + const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little); const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little); - try w.print("{x: >4} {x: >4} ", .{ export_dir.ordinal_base + ord, name_i }); + try w.print("{f} {f} ", .{ + fmtIntField(d, @as(u16, @intCast(export_dir.ordinal_base + ord)), .{ .kind = .ord }), + fmtIntField(d, @as(u16, @intCast(name_i)), .{ .kind = .ord }), + }); const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva; if (is_forwarder) { try w.writeAll("forwards"); } else { - try w.print("{x: >8}", .{addr}); + try w.print("{f}", .{fmtIntField(d, addr, .{ .kind = .rva })}); } - const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0); try w.print(" | {s}", .{name}); if (is_forwarder) try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)}); @@ -1110,11 +1211,9 @@ const coff = struct { } } - if (opts.imports) { + if (d.opts.imports) { if (try seekToDataDirectory( - opts, - fr, - w, + d, rva_index, sections.items, image_info.?.data_dirs, @@ -1125,8 +1224,7 @@ const coff = struct { defer directory_entries.deinit(gpa); while (true) { const entry = r.takeStruct(Entry, .little) catch |err| - return failParse( - opts, + return d.failParse( "unable to read import directory entry {x}: {t}", .{ directory_entries.items.len, err }, ); @@ -1141,8 +1239,7 @@ const coff = struct { sections.items, entry.name_rva, ) orelse - return failParse( - opts, + return d.failParse( "import directory entry name rva 0x{x} was not found in any section", .{entry.name_rva}, ); @@ -1151,8 +1248,7 @@ const coff = struct { entry.name_rva, ) catch unreachable; fr.seekTo(name_loc) catch |err| - return failParse( - opts, + return d.failParse( "unable to seek to import directory entry name at 0x{x}: {t}", .{ name_loc, err }, ); @@ -1160,7 +1256,7 @@ const coff = struct { const dll_name = (try r.takeDelimiter(0)).?; try w.print("Import table entry for {s}:\n", .{dll_name}); - try dumpHeader(w, Entry, &entry, struct {}); + try dumpHeader(d, Entry, &entry, struct {}); try w.print( \\ @@ -1173,8 +1269,7 @@ const coff = struct { sections.items, entry.import_lookup_table_rva, ) orelse - return failParse( - opts, + return d.failParse( "import directory entry ilt rva 0x{x} was not found in any section", .{entry.import_lookup_table_rva}, ); @@ -1183,8 +1278,7 @@ const coff = struct { entry.import_lookup_table_rva, ) catch unreachable; fr.seekTo(ilt_loc) catch |err| - return failParse( - opts, + return d.failParse( "unable to seek to import directory ilt at 0x{x}: {t}", .{ ilt_loc, err }, ); @@ -1199,8 +1293,7 @@ const coff = struct { defer ilt_entries.deinit(gpa); while (true) { const table_entry = r.takeStruct(TableEntry, .little) catch |err| - return failParse( - opts, + return d.failParse( "unable to read ilt entry {s}:{x}: {t}", .{ dll_name, ilt_entries.items.len, err }, ); @@ -1217,8 +1310,7 @@ const coff = struct { sections.items, ilt_entry.payload.hint_name_rva, ) orelse - return failParse( - opts, + return d.failParse( "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section", .{ ilt_entry_i, ilt_entry.payload.hint_name_rva }, ); @@ -1227,22 +1319,19 @@ const coff = struct { ilt_entry.payload.hint_name_rva, ) catch unreachable; fr.seekTo(hint_loc) catch |err| - return failParse( - opts, + return d.failParse( "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}", .{ ilt_entry_i, hint_loc, err }, ); const hint = r.takeInt(u16, .little) catch |err| - return failParse( - opts, + return d.failParse( "unable to read import directory ilt entry 0x{x}'s hint: {t}", .{ ilt_entry_i, err }, ); const name = r.takeDelimiter(0) catch |err| - return failParse( - opts, + return d.failParse( "unable to read import directory ilt entry 0x{x}'s name: {t}", .{ ilt_entry_i, err }, ); @@ -1250,18 +1339,16 @@ const coff = struct { try w.print(" {x: >4} | {s}\n", .{ hint, name.? }); } } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); }, } } } } - if (opts.tls) { + if (d.opts.tls) { if (try seekToDataDirectory( - opts, - fr, - w, + d, rva_index, sections.items, image_info.?.data_dirs, @@ -1272,10 +1359,10 @@ const coff = struct { inline else => |m| { const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m); const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err| - return failParse(opts, "unable to read tls directory: {t}", .{err}); + return d.failParse("unable to read tls directory: {t}", .{err}); try w.writeAll("TLS Directory:\n"); - try dumpHeader(w, TlsDirectoryEntry, &tls_entry, struct {}); + try dumpHeader(d, TlsDirectoryEntry, &tls_entry, struct {}); try w.writeAll(" | "); if (tls_entry.characteristics.alignment == .NONE) { @@ -1301,8 +1388,7 @@ const coff = struct { sections.items, callbacks_rva, ) orelse - return failParse( - opts, + return d.failParse( "tls callbacks rva 0x{x} was not found in any section", .{callbacks_rva}, ); @@ -1311,24 +1397,22 @@ const coff = struct { .rvaFileOffset(callbacks_rva) catch unreachable; fr.seekTo(callbacks_loc) catch |err| - return failParse( - opts, + return d.failParse( "unable to seek to tls callbacks array at offset 0x{x}: {t}", .{ callbacks_loc, err }, ); while (true) { const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err| - return failParse( - opts, + return d.failParse( "unable to read tls callbacks array: {t}", .{err}, ); - try w.print("{x: >16} \n", .{callback_va}); + try w.print("{f}\n", .{fmtIntField(d, callback_va, .{ .kind = .va })}); if (callback_va == 0) break; } - try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); }, } } @@ -1336,9 +1420,7 @@ const coff = struct { } fn seekToDataDirectory( - opts: *const Options, - fr: *Io.File.Reader, - w: *Io.Writer, + d: *const DumpContext, rva_index: []const u16, sections: []const Section, data_dirs: []const std.coff.ImageDataDirectory, @@ -1349,16 +1431,14 @@ const coff = struct { if (rva == 0) break :blk; const section_index = sectionContainingRva(rva_index, sections, rva) orelse - return failParse( - opts, + return d.failParse( "{t} directory rva 0x{x} was not found in any section", .{ entry, rva }, ); const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable; - fr.seekTo(file_offset) catch |err| - return failParse( - opts, + d.fr.seekTo(file_offset) catch |err| + return d.failParse( "unable to seek to {t} directory at offset 0x{x}: {t}", .{ entry, file_offset, err }, ); @@ -1366,7 +1446,7 @@ const coff = struct { return section_index; } - try w.print("{t} directory was not present in optional header\n", .{entry}); + try d.w.print("{t} directory was not present in optional header\n", .{entry}); return null; } @@ -1382,19 +1462,18 @@ const coff = struct { fn order(ctx: @This(), section_index: u16) std.math.Order { const h = &ctx.sections[section_index].header; - const start = h.virtual_address; - if (ctx.rva < start) return .lt; + if (ctx.rva < h.virtual_address) return .lt; const end = h.virtual_address + h.size_of_raw_data; if (ctx.rva >= end) return .gt; return .eq; } }; - const index = std.sort.binarySearch(u16, indices, Context{ + const indices_index = std.sort.binarySearch(u16, indices, Context{ .rva = rva, .sections = sections, }, Context.order) orelse return null; - return @intCast(index); + return @intCast(indices[indices_index]); } fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 { @@ -1427,6 +1506,40 @@ const coff = struct { }; } + const FormatIntField = struct { + val: ?u64, + width: usize, + zero_fill: bool, + }; + + fn fmtIntField( + d: *const DumpContext, + val: anytype, + params: struct { + kind: ?FieldKind = null, + width: ?usize = null, + zero_fill: bool = false, + }, + ) std.fmt.Alt(FormatIntField, intFieldString) { + return .{ + .data = .{ + .val = if (d.redacted(params.kind)) null else val, + .width = params.width orelse @typeInfo(@TypeOf(val)).int.bits / 4, + .zero_fill = params.zero_fill, + }, + }; + } + + fn intFieldString(field: FormatIntField, w: *std.Io.Writer) std.Io.Writer.Error!void { + if (field.val) |val| { + try w.printInt(val, 16, .lower, .{ + .width = field.width, + .alignment = .right, + .fill = if (field.zero_fill) '0' else ' ', + }); + } else try w.splatByteAll('x', field.width); + } + fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void { const s = @typeInfo(T).@"struct"; inline for (s.fields) |flag_field| { @@ -1437,33 +1550,53 @@ const coff = struct { } } - fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void { - try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); - try dumpHeader(w, ArchiveHeader, header, struct { - pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {} - pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void { - try cw.print("{o: >16} file_mode\n", .{h.file_mode}); + fn dumpArchiveHeader(d: *const DumpContext, header: *const ArchiveHeader, pos: u32) !void { + try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); + try dumpHeader(d, ArchiveHeader, header, struct { + pub fn name(_: *const DumpContext, _: *const ArchiveHeader) !void {} + pub fn file_mode(id: *const DumpContext, h: *const ArchiveHeader) !void { + try id.w.print("{o: >16} file_mode\n", .{h.file_mode}); } }); } - fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void { + fn fieldKind(name: []const u8) ?FieldKind { + if (std.mem.endsWith(u8, name, "_rva")) + return .rva; + if (std.mem.endsWith(u8, name, "_va") or + std.mem.endsWith(u8, name, "_address") or + std.mem.startsWith(u8, name, "pointer_")) + return .va; + if (std.mem.startsWith(u8, name, "number_")) + return .size; + return null; + } + + fn dumpHeader( + d: *const DumpContext, + comptime T: type, + header: *const T, + Custom: type, + ) !void { inline for (@typeInfo(T).@"struct".fields) |field| { const val = &@field(header, field.name); if (@hasDecl(Custom, field.name)) { - try @field(Custom, field.name)(header, w); + try @field(Custom, field.name)(d, header); } else { switch (@typeInfo(field.type)) { - .int => try w.print("{x: >16} {s}\n", .{ val.*, field.name }), - .@"enum" => try w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }), + .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{ + .kind = comptime fieldKind(field.name), + .width = 16, + }), field.name }), + .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }), .@"struct" => |s| { switch (s.layout) { .auto, .@"extern", - => try dumpHeader(w, field.type, val, Custom), + => try dumpHeader(d, field.type, val, Custom), .@"packed" => { - try w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name }); - try dumpFlags(w, "| {s}\n", field.type, val, 15); + try d.w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name }); + try dumpFlags(d.w, "| {s}\n", field.type, val, 15); }, } }, @@ -1477,8 +1610,12 @@ const coff = struct { try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name }); } - fn dumpRvaField(w: *Io.Writer, name: []const u8, rva: u64, base: u64) !void { - try w.print("{x: >16} {s} ({x})\n", .{ rva, name, base + rva }); + fn dumpRvaField(d: *const DumpContext, name: []const u8, rva: u64, base: u64) !void { + try d.w.print("{f} {s} ({f})\n", .{ + fmtIntField(d, rva, .{ .kind = .rva }), + name, + fmtIntField(d, base + rva, .{ .kind = .va }), + }); } }; @@ -1492,18 +1629,32 @@ const usage = \\Usage: zig objdump [options] file \\ \\Options: - \\ -h, --help Print this help and exit - \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols - \\ --file-headers Display file-format specific headers - \\ --imports Display imported symbols - \\ --exports Display exported symbols - \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified linker member (default 2) - \\ --member-headers Display archive member headers - \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times. - \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times. - \\ --relocs Display relocations - \\ --section-headers Display section headers - \\ --strings Display string tables - \\ --symbols Display symbol tables - \\ --tls Display TLS information + \\ -h, --help Print this help and exit + \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols + \\ --file-headers Display file-format specific headers + \\ --imports Display imported symbols + \\ --exports Display exported symbols + \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2) + \\ --member-headers Display archive member headers + \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing. + \\ rva Relative virtual addresses + \\ va Virtual addresses and file offsets + \\ ord Symbol ordinals / hints + \\ size Sizes and lengths + \\ all All of the above + \\ --omit-element=[kind] Omit specific parts of the output. Intended for snapshot testing. + \\ file-type File type summary + \\ table-headers Table headers with column names + \\ header-names Name that precedes a header block + \\ newlines Newlines between output sections + \\ all All of the above + \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times. + \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times. + \\ --only-symbol=[name] Only consider symbol names that contain [name]. Can be specified multiple times. + \\ --relocs Display relocations + \\ -s, --snapshot Alias for --redact=all --omit-format=all + \\ --section-headers Display section headers + \\ --strings Display string tables + \\ --symbols Display symbol tables + \\ --tls Display TLS information ; diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 236fa4671dcf6490a466db1b4a040d39988dabc6..bbcac0b61c632c52d190413609940c599d855916 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -585,6 +585,8 @@ pub const Step = extern struct { expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes), expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes), expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32), + expect_stdout_snapshot: Storage.FlagOptional(.flags2, .expect_stdout_snapshot, LazyPath.Index), + expect_stderr_snapshot: Storage.FlagOptional(.flags2, .expect_stderr_snapshot, LazyPath.Index), pub const CapturedStream = extern struct { generated_file: GeneratedFileIndex, @@ -683,7 +685,9 @@ pub const Step = extern struct { expect_stdout_match: bool, expect_term: bool, expect_term_status: ExpectTermStatus, - _: u25 = 0, + expect_stdout_snapshot: bool, + expect_stderr_snapshot: bool, + _: u23 = 0, }; }; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 4ace7803225ea6e14caf26bf6769bf4a061567a0..f33daf70203b5af131fbc646866f0ff2dde9dffa 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -129,6 +129,8 @@ pub const StdIo = union(enum) { expect_stdout_exact: []const u8, expect_stdout_match: []const u8, expect_term: process.Child.Term, + expect_stderr_snapshot: std.Build.LazyPath, + expect_stdout_snapshot: std.Build.LazyPath, }; }; @@ -632,6 +634,13 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void { .check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"), else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"), } + + switch (new_check) { + .expect_stderr_snapshot, + .expect_stdout_snapshot, + => |file| run.addFileInput(file), + else => {}, + } } pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { diff --git a/test/link.zig b/test/link.zig index 2a5cf4ad44496efe58845842e5883c36fdb6b218..65ec8f4e7e2ea7f5f7b7bed6de646ed1a5b9a329 100644 --- a/test/link.zig +++ b/test/link.zig @@ -1,17 +1,30 @@ -pub fn addCases(cases: @import("tests.zig").LinkContext) void { - if (cases.addTestStep("static-lib-exports")) |name| { - const lib = cases.addStaticLibrary(.{ +pub fn addCases(ctx: *@import("tests.zig").LinkContext) void { + if (ctx.includeTest("exports-static")) |prefix| { + const lib = ctx.addLibrary(.static, .{ .name = "lib", - .zig_source_bytes = - \\export fn foo() void {} - \\var bar: u32 = 1234; - \\comptime { @export(&bar, .{ .name = "bar", .linkage = .strong }); } - \\const baz: u64 = 5678; - \\comptime { @export(&baz, .{ .name = "baz", .linkage = .strong }); } - , + .zig_source_file = ctx.sourcePath("exports.zig"), }); - cases.verifyObjdump(name, lib, &.{"--symbols"}, .{ .os = true }); + ctx.verifyObjdump(prefix, lib, &.{ + "-s", + "--symbols", + "--only-symbol=foo", + }, .{}); } + + if (ctx.includeTest("exports-dynamic")) |prefix| { + const lib = ctx.addLibrary(.dynamic, .{ + .name = "lib", + .zig_source_file = ctx.sourcePath("exports.zig"), + }); + ctx.verifyObjdump(prefix, lib, &.{ + "-s", + "--exports", + "--only-symbol=foo", + }, .{}); + } + + + } const std = @import("std"); diff --git a/test/link/exports.zig b/test/link/exports.zig new file mode 100644 index 0000000000000000000000000000000000000000..3f5e4734f45cc1e29472480174beb1d3d90e23cb --- /dev/null +++ b/test/link/exports.zig @@ -0,0 +1,9 @@ +export fn foo_fn() void {} +var foo_var: u32 = 1234; +comptime { + @export(&foo_var, .{ .name = "foo_var", .linkage = .strong }); +} +const foo_const: u64 = 5678; +comptime { + @export(&foo_const, .{ .name = "foo_const", .linkage = .strong }); +} diff --git a/test/link/snapshots/exports-dynamic.lib.dmp b/test/link/snapshots/exports-dynamic.lib.dmp new file mode 100644 index 0000000000000000000000000000000000000000..fbd6415e5f9564d2299ab13ca81b75dde3100001 --- /dev/null +++ b/test/link/snapshots/exports-dynamic.lib.dmp @@ -0,0 +1,14 @@ +Export directory: + 0 flags + 0 time_date_stamp + 0.00 version +xxxxxxxxxxxxxxxx name_rva + 1 ordinal_base +xxxxxxxxxxxxxxxx number_of_entries +xxxxxxxxxxxxxxxx number_of_names +xxxxxxxxxxxxxxxx export_address_table_rva +xxxxxxxxxxxxxxxx name_pointer_table_rva +xxxxxxxxxxxxxxxx ordinal_table_rva +xxxx xxxx xxxxxxxx | foo_const +xxxx xxxx xxxxxxxx | foo_fn +xxxx xxxx xxxxxxxx | foo_var diff --git a/test/link/snapshots/exports-static.lib.dmp b/test/link/snapshots/exports-static.lib.dmp new file mode 100644 index 0000000000000000000000000000000000000000..d112fcbe7191bc481d50ad89592d86e2d0b4b576 --- /dev/null +++ b/test/link/snapshots/exports-static.lib.dmp @@ -0,0 +1,3 @@ +xxxx 00000000 4 NULL() EXTERNAL | foo_fn +xxxx 00000000 2 NULL EXTERNAL | foo_var +xxxx 00000008 3 NULL EXTERNAL | foo_const diff --git a/test/src/Link.zig b/test/src/Link.zig index ce41727601798f25f39efa5a49bd5aa634a3a729..adcc329b6a98e54667002b3236dde144e39ba752 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -5,21 +5,29 @@ target: std.Build.ResolvedTarget, use_llvm: bool, use_lld: bool, link_libc: bool, -suffix: []const u8, test_filters: []const []const u8, +update_step: ?*Step.UpdateSourceFiles, +updated_snapshots: std.StringArrayHashMapUnmanaged(void), max_rss: usize, -pub fn addTestStep(self: *const Link, prefix: []const u8) ?[]const u8 { +pub fn includeTest(self: *const Link, prefix: []const u8) ?[]const u8 { if (for (self.test_filters) |filter| { if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false; } else self.test_filters.len > 0) return null; + return prefix; +} - return std.fmt.allocPrint(self.b.allocator, "test-{s}", .{prefix}) catch @panic("OOM"); +pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath { + return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path })); } -pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compile { +pub fn addLibrary( + self: *const Link, + linkage: std.builtin.LinkMode, + overlay: OverlayOptions, +) *Step.Compile { return self.b.addLibrary(.{ - .linkage = .static, + .linkage = linkage, .name = overlay.name, .root_module = self.createModule(overlay), .use_llvm = self.use_llvm, @@ -27,7 +35,6 @@ pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compil }); } -// TODO: Use std.meta.FieldEnum on TargetQuery? const SnapshotScope = packed struct { arch: bool = false, os: bool = false, @@ -38,33 +45,44 @@ const SnapshotScope = packed struct { link_libc: bool = false, }; +/// Verify the results of a `zig objdump` call against a snapshot, which +/// contains the expected output. Snapshots alias between all build +/// configurations by default, but by specifying fields in `scope`, +/// unique snapshot names are generated for each value of that field. pub fn verifyObjdump( - self: *const Link, - name: []const u8, + self: *Link, + prefix: []const u8, compile: *Step.Compile, args: []const []const u8, scope: SnapshotScope, ) void { - const snapshot_name = self.snapshotName(name, compile.name, scope) catch @panic("OOM"); + const snapshot_name = self.snapshotName(prefix, compile.name, scope) catch @panic("OOM"); + const snapshot_sub_path = self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name }); + + // Many tests may read the same snapshot, so only use the first one to update. + // If there are differences in output, they will show up on the next test run. + if (self.update_step != null) { + const gop = self.updated_snapshots.getOrPut(self.b.allocator, snapshot_sub_path) catch @panic("OOM"); + if (gop.found_existing) return; + } + const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name})); run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" }); run_step.addArtifactArg(compile); run_step.addArgs(args); run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); - const actual_path = run_step.captureStdOut(.{ .trim_whitespace = .none }); - const expected_path = self.b.path(self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name })); + if (self.update_step) |update_step| { + // Workaround for the build system not realizing objdump itself has changed + run_step.has_side_effects = true; - const check_step = self.b.addCheckFile(actual_path, .{ - .expected_file = .{ - .file = expected_path, - .if_missing = .fail, - // TODO: Option to do UpdateSourceFiles if not matching / missing? - // TODO: Option to output to -.actual.dmp file? - }, - }); + const snapshot_update_path = run_step.captureStdOut(.{}); + update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path); + } else { + run_step.addCheck(.{ .snapshot = .{ .file = self.b.path(snapshot_sub_path) } }); + } - self.step.dependOn(&check_step.step); + self.step.dependOn(&run_step.step); } fn snapshotName( @@ -81,9 +99,9 @@ fn snapshotName( if (scope.os) try w.print("-{t}", .{self.target.result.os.tag}); if (scope.abi) try w.print("-{t}", .{self.target.result.abi}); if (scope.optimize) try w.print("-{t}", .{self.optimize}); - if (scope.use_llvm and self.use_llvm) try w.writeAll("-llvm"); - if (scope.use_lld and self.use_lld) try w.writeAll("-lld"); - if (scope.link_libc and self.link_libc) try w.writeAll("-libc"); + if (scope.use_llvm) try w.writeAll(if (self.use_llvm) "-llvm" else "-no-llvm"); + if (scope.use_lld) try w.writeAll(if (self.use_lld) "-lld" else "-no-lld"); + if (scope.link_libc) try w.writeAll(if (self.link_libc) "-libc" else "-no-libc"); try w.writeAll(".dmp"); return try snapshot_name.toOwnedSlice(); @@ -95,7 +113,7 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { const mod = self.b.createModule(.{ .target = self.target, .optimize = self.optimize, - .root_source_file = rsf: { + .root_source_file = overlay.zig_source_file orelse rsf: { const bytes = overlay.zig_source_bytes orelse break :rsf null; const name = self.b.fmt("{s}.zig", .{overlay.name}); break :rsf write_files.add(name, bytes); @@ -148,6 +166,7 @@ const OverlayOptions = struct { objcpp_source_bytes: ?[]const u8 = null, objcpp_source_flags: []const []const u8 = &.{}, zig_source_bytes: ?[]const u8 = null, + zig_source_file: ?std.Build.LazyPath = null, pic: ?bool = null, strip: ?bool = null, }; diff --git a/test/tests.zig b/test/tests.zig index 625fe48c22baf5ce299f0c79a7f9ced3fa7ebe30..c3620b0bf0672140d7935b2f8b49802d36ee8e40 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3148,6 +3148,11 @@ const LinkTestOptions = struct { pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step { const step = b.step("test-link", "Run the linker tests"); + const update_snapshots = b.option( + bool, + "link-snapshot-update", + "Update linker test snapshots in-place instead of testing against them", + ) orelse false; for (link_targets) |link_target| { if (options.skip_non_native and !link_target.target.isNative()) continue; @@ -3168,24 +3173,34 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step { if (options.skip_llvm and would_use_llvm) continue; if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue; - link.addCases(.{ + const opt_update_step = if (update_snapshots) update: { + const update_step = Step.UpdateSourceFiles.create(b); + step.dependOn(&update_step.step); + break :update update_step; + } else null; + + var context: LinkContext = .{ .b = b, .step = step, .optimize = optimize_mode, .target = resolved_target, - .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{ - target.zigTriple(b.allocator) catch @panic("OOM"), - optimize_mode, - if (link_target.use_llvm) "-llvm" else "", - if (link_target.use_lld) "-lld" else "", - if (link_target.link_libc) "-libc" else "", - }) catch @panic("OOM"), + // .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{ + // target.zigTriple(b.allocator) catch @panic("OOM"), + // optimize_mode, + // if (link_target.use_llvm) "-llvm" else "", + // if (link_target.use_lld) "-lld" else "", + // if (link_target.link_libc) "-libc" else "", + // }) catch @panic("OOM"), .use_llvm = link_target.use_llvm, .use_lld = link_target.use_lld, .link_libc = link_target.link_libc, .test_filters = options.test_filters, + .update_step = opt_update_step, + .updated_snapshots = .empty, .max_rss = options.max_rss, - }); + }; + + link.addCases(&context); } } return step; -- 2.54.0 From 2c3f42724d5131d826674357cecb5731d858ad27 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 62/94] objdump: rework --omit-element= as --elements= for more flexibility --- lib/compiler/objdump.zig | 106 +++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 69e19e2ac38ce836a6e7689e898418b63e53763d..0202998db237c83ceef7ebd07159c76a442cc438 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -16,7 +16,7 @@ const Options = struct { input_path: []const u8, member_filters: []const []const u8 = &.{}, member_headers: bool, - omit_elements: std.enums.EnumArray(Element, bool), + elements: std.enums.EnumArray(Element, bool), redact: std.enums.EnumArray(FieldKind, bool), relocs: bool, section_filters: []const []const u8 = &.{}, @@ -39,9 +39,10 @@ const FieldKind = enum { const Element = enum { @"file-type", - @"table-header", - @"header-names", + @"header-name", + @"member-path", newlines, + @"table-header", }; pub fn main(init: std.process.Init) !void { @@ -57,7 +58,8 @@ pub fn main(init: std.process.Init) !void { var opt_input_path: ?[]const u8 = null; var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null; var opt_member_headers: ?bool = null; - var omit_elements: @FieldType(Options, "omit_elements") = .initFill(false); + var any_elements = false; + var elements: ?@FieldType(Options, "elements") = null; var redact: @FieldType(Options, "redact") = .initFill(false); var opt_relocs: ?bool = null; var opt_section_headers: ?bool = null; @@ -93,14 +95,23 @@ pub fn main(init: std.process.Init) !void { opt_linker_member = .second_linker; } else if (mem.eql(u8, arg, "--member-headers")) { opt_member_headers = true; - } else if (mem.startsWith(u8, arg, "--omit-element=")) { - const kind = arg["--omit-element=".len..]; - if (std.meta.stringToEnum(Element, kind)) |format_kind| { - omit_elements.set(format_kind, true); - } else if (std.mem.eql(u8, kind, "all")) { - omit_elements = .initFill(true); - } else { - fatal("unrecognized element: {s}", .{kind}); + } else if (mem.startsWith(u8, arg, "--elements=")) { + any_elements = true; + var split = std.mem.splitScalar(u8, arg["--elements=".len..], ','); + while (split.next()) |element| { + const kind, const add = if (element.len > 0 and element[0] == '-') + .{ element[1..], false } + else + .{ element, true }; + + if (elements == null) elements = .initFill(false); + if (std.meta.stringToEnum(Element, kind)) |format_kind| { + elements.?.set(format_kind, add); + } else if (std.mem.eql(u8, kind, "all")) { + elements.? = .initFill(add); + } else { + fatal("unrecognized element: '{s}'", .{kind}); + } } } else if (mem.startsWith(u8, arg, "--only-member=")) { (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]); @@ -122,7 +133,7 @@ pub fn main(init: std.process.Init) !void { } else if (mem.eql(u8, arg, "--section-headers")) { opt_section_headers = true; } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--snapshot")) { - omit_elements = .initFill(true); + elements = .initFill(false); redact = .initFill(true); } else if (mem.eql(u8, arg, "--strings")) { opt_strings = true; @@ -148,7 +159,7 @@ pub fn main(init: std.process.Init) !void { .linker_member = opt_linker_member, .member_filters = member_filters.items, .member_headers = opt_member_headers orelse false, - .omit_elements = omit_elements, + .elements = elements orelse .initFill(true), .redact = redact, .relocs = opt_relocs orelse false, .section_filters = section_filters.items, @@ -215,20 +226,26 @@ fn dump(d: *const DumpContext) !void { return error.ParseFailure; } - if (d.element(.@"file-type")) + if (d.element(.@"file-type")) { try d.w.print("{s}: PE/COFF image\n\n", .{basename}); + if (d.element(.newlines)) try d.w.writeByte('\n'); + } return coff.dumpObject(d, true, basename); } else if (std.mem.eql(u8, ext, ".lib")) { r.fill(std.coff.archive_signature.len) catch break :coff; if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; - if (d.element(.@"file-type")) - try d.w.print("{s}: COFF archive\n\n", .{basename}); + if (d.element(.@"file-type")) { + try d.w.print("{s}: COFF archive\n", .{basename}); + if (d.element(.newlines)) try d.w.writeByte('\n'); + } return coff.dumpArchive(d); } else if (std.mem.eql(u8, ext, ".obj")) { - if (d.element(.@"file-type")) - try d.w.print("{s}: COFF object\n\n", .{basename}); + if (d.element(.@"file-type")) { + try d.w.print("{s}: COFF object\n", .{basename}); + if (d.element(.newlines)) try d.w.writeByte('\n'); + } return coff.dumpObject(d, false, basename); } @@ -243,7 +260,7 @@ const DumpContext = struct { w: *Io.Writer, fn element(self: *const DumpContext, e: Element) bool { - return !self.opts.omit_elements.get(e); + return self.opts.elements.get(e); } fn redacted(self: *const DumpContext, opt_kind: ?FieldKind) bool { @@ -589,9 +606,19 @@ const coff = struct { d.opts.strings or d.opts.symbols) { - if (d.element(.@"file-type")) - try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(d.opts.input_path), header.name }); - try dumpObject(d, false, header.name); + const member_name = if (d.element(.@"member-path")) + header.name + else + std.fs.path.basename(header.name); + + if (d.element(.@"file-type")) { + try w.print("{s}({s}): COFF object\n", .{ + std.fs.path.basename(d.opts.input_path), + member_name, + }); + if (d.element(.newlines)) try w.writeByte('\n'); + } + try dumpObject(d, false, member_name); } } } @@ -611,7 +638,7 @@ const coff = struct { return d.failParse("unable to read COFF header: {t}", .{err}); if (d.opts.file_headers) { - if (d.element(.@"header-names")) try w.writeAll("COFF Header:\n"); + if (d.element(.@"header-name")) try w.writeAll("COFF Header:\n"); try dumpHeader(d, std.coff.Header, &header, struct {}); if (d.element(.newlines)) try w.writeByte('\n'); } @@ -639,7 +666,7 @@ const coff = struct { break :image_info null; } - if (d.opts.file_headers and d.element(.@"header-names")) + if (d.opts.file_headers and d.element(.@"header-name")) try w.writeAll("COFF Optional Header:\n"); const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); @@ -701,7 +728,7 @@ const coff = struct { else => return d.failParse("invalid optional header magic number: {x}", .{magic}), }; - if (d.opts.file_headers and d.element(.@"header-names")) + if (d.opts.file_headers and d.element(.@"header-name")) try w.writeAll("Data Directories:\n"); for (0..num_directory_entries) |dir_i| { @@ -753,7 +780,7 @@ const coff = struct { \\ , .{string_table.len}); - var sr = Io.Reader.fixed(string_table[4..]); + var sr = Io.Reader.fixed(string_table[@sizeOf(u32)..]); while (try sr.takeDelimiter(0)) |str| { try w.writeAll(str); try w.writeByte('\n'); @@ -1631,28 +1658,29 @@ const usage = \\Options: \\ -h, --help Print this help and exit \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols + \\ --exports Display exported symbols \\ --file-headers Display file-format specific headers \\ --imports Display imported symbols - \\ --exports Display exported symbols \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2) \\ --member-headers Display archive member headers - \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing. - \\ rva Relative virtual addresses - \\ va Virtual addresses and file offsets - \\ ord Symbol ordinals / hints - \\ size Sizes and lengths - \\ all All of the above - \\ --omit-element=[kind] Omit specific parts of the output. Intended for snapshot testing. + \\ --elements=[e1],[e2],-[e3],... Select which formatting elements are displayed. Intended for snapshot testing. \\ file-type File type summary - \\ table-headers Table headers with column names - \\ header-names Name that precedes a header block + \\ header-name Name that precedes a header block + \\ member-path Display full member paths. If removed, only basenames will be used. \\ newlines Newlines between output sections - \\ all All of the above + \\ table-header Table headers with column names + \\ all (default) All of the above \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times. \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times. \\ --only-symbol=[name] Only consider symbol names that contain [name]. Can be specified multiple times. + \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing. + \\ rva Relative virtual addresses + \\ va Virtual addresses and file offsets + \\ ord Symbol ordinals / hints + \\ size Sizes and lengths + \\ all All of the above \\ --relocs Display relocations - \\ -s, --snapshot Alias for --redact=all --omit-format=all + \\ -s, --snapshot Alias for --redact=all --elements=-all \\ --section-headers Display section headers \\ --strings Display string tables \\ --symbols Display symbol tables -- 2.54.0 From 7be8e660acaa56d2be110d59b058ff2a31b05e23 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 63/94] Coff: value / extra rework to allow symbols to have both an alias and symbol table entry Coff: move pending_shrink handling to flush, as it's a special case of a resolve task that generates idle tasks (moves / resizes). It's also redundant to do this operation more than once. --- src/link/Coff.zig | 260 +++++++++++++++++++++++++++------------------- 1 file changed, 152 insertions(+), 108 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 1a78ab79711428ca9bc59d5015e4377498e07d2b..bdd7aa56c837dc562d051d4433b60681315459b9 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -874,14 +874,15 @@ pub const Symbol = struct { ni: MappedFile.Node.Index, rva: u32, value: std.meta.BareUnion(Symbol.Value), + extra: std.meta.BareUnion(Symbol.Extra), flags: packed struct(u16) { value_tag: ValueTag, + extra_tag: ExtraTag, type: Symbol.Type, dll_storage_class: DllStorageClass, // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - has_alias: bool, - _: u7 = 0, + _: u6 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -889,16 +890,6 @@ pub const Symbol = struct { target_relocs: Reloc.Index, section_number: SectionNumber, gmi: Node.GlobalMapIndex, - extra: union { - /// Only valid when outputting objects - sti: SymbolTable.Index, - /// Only valid when .ni == .input_section and .value_tag == .node_offset - /// TODO: This is only used for name lookups, could just be String? - isli: Node.InputSection.LocalIndex, - /// Only valid if flags.has_alias is set. - /// The next symbol in the list of aliases of this symbol. - next_alias_si: Symbol.Index, - }, pub const DllStorageClass = enum(u2) { default, @@ -916,23 +907,41 @@ pub const Symbol = struct { node_offset, weak_alias_si, weak_alias_name, - size, + sti, }; pub const Value = union(ValueTag) { /// The offset of the symbol within its node. Used with symbols that /// don't create their own nodes: .input_section, .import_address_table + /// Images only. node_offset: u32, /// This is a weak alias that can replace this symbol - /// Globals only. + /// Globals only, images only. weak_alias_si: Symbol.Index, /// For weak externals that have an alias that is also an undef /// external, this is the name of the alias global that should /// be generated if this symbol is not resolved. - /// Globals only. + /// Globals only, images only. weak_alias_name: String, - /// The symbol size, or 0 if unknown + /// Index of this symbol in the symbol table + /// Only used when outputting objects + sti: SymbolTable.Index, + }; + + const ExtraTag = enum(u2) { + size, + isli, + next_alias_si, + }; + + pub const Extra = union(ExtraTag) { + // The size of the symbol size: u32, + /// Only valid when .ni == .input_section and .value_tag == .node_offset + /// TODO: This is only used for name lookups, could just be String? + isli: Node.InputSection.LocalIndex, + /// The next symbol in the list of aliases of this symbol. + next_alias_si: Symbol.Index, }; pub fn setValue(sym: *Symbol, value: Symbol.Value) void { @@ -946,6 +955,17 @@ pub const Symbol = struct { }; } + pub fn setExtra(sym: *Symbol, extra: Symbol.Extra) void { + sym.flags.extra_tag = std.meta.activeTag(extra); + sym.extra = switch (sym.flags.extra_tag) { + inline else => |t| @unionInit( + @FieldType(Symbol, "extra"), + @tagName(t), + @field(extra, @tagName(t)), + ), + }; + } + pub fn nodeOffset(sym: *const Symbol, coff: *Coff) u32 { return switch (sym.flags.value_tag) { .node_offset => offset: { @@ -961,7 +981,7 @@ pub const Symbol = struct { } pub fn size(sym: *const Symbol) u32 { - return if (sym.flags.value_tag == .size) sym.value.size else 0; + return if (sym.flags.extra_tag == .size) sym.extra.size else 0; } pub const SectionNumber = enum(i16) { @@ -1038,7 +1058,7 @@ pub const Symbol = struct { si.applyTargetRelocs(coff, .none); var alias_sym = sym; - while (alias_sym.flags.has_alias) { + while (alias_sym.flags.extra_tag == .next_alias_si) { const alias_si = alias_sym.extra.next_alias_si; alias_sym = alias_si.get(coff); assert(alias_sym.ni == sym.ni); @@ -1049,7 +1069,7 @@ pub const Symbol = struct { pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); - const index = sym.extra.sti.unwrap() orelse return; + const index = sym.value.sti.unwrap() orelse return; var ri = sym.target_relocs; while (ri != .none) { const reloc = ri.get(coff); @@ -1989,6 +2009,7 @@ fn initHeaders( .resized = true, }); coff.nodes.appendAssumeCapacity(.string_table); + coff.targetStore(coff.symbolTableStringLenPtr(), @sizeOf(u32)); } try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count); @@ -2193,8 +2214,7 @@ pub fn initBuiltins(coff: *Coff) !void { list_sym.ni = start_sym.ni; list_sym.section_number = start_sym.section_number; - start_sym.extra = .{ .next_alias_si = list_si }; - start_sym.flags.has_alias = true; + start_sym.setExtra(.{ .next_alias_si = list_si }); } } } @@ -2483,11 +2503,14 @@ pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.c return null; } -pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) *align(2) std.coff.SectionDefinition { - const sti = si.get(coff).extra.sti; - const entry = symbolTableEntryPtr(coff, sti).?; - assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1); - return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); +pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.SectionDefinition { + const sti = si.get(coff).value.sti; + if (symbolTableEntryPtr(coff, sti)) |entry| { + assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1); + return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); + } else { + return null; + } } pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 { @@ -2524,19 +2547,19 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { defer coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, .rva = 0, - .value = .{ .size = 0 }, + .value = .{ .sti = .none }, + .extra = .{ .size = 0 }, .flags = .{ - .value_tag = .size, + .value_tag = .sti, + .extra_tag = .size, .type = .unknown, .dll_storage_class = .default, .weak_external_strat = undefined, - .has_alias = false, }, .loc_relocs = .none, .target_relocs = .none, .section_number = .UNDEFINED, .gmi = .none, - .extra = .{ .sti = .none }, }; return @enumFromInt(coff.symbols.items.len); } @@ -2975,7 +2998,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void const sym = si.get(coff); assert(sym.ni != .none or sym.gmi != .none); - const entry = coff.symbolTableEntryPtr(sym.extra.sti) orelse entry: { + const entry = coff.symbolTableEntryPtr(sym.value.sti) orelse entry: { var buf: [15]u8 = undefined; const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { @@ -3040,10 +3063,11 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); - sym.extra = .{ .sti = .wrap(old_num_symbols) }; + + sym.value.sti = .wrap(old_num_symbols); si.flushSymbolTableIndex(coff); - const entry = coff.symbolTableEntryPtr(sym.extra.sti).?; + const entry = coff.symbolTableEntryPtr(sym.value.sti).?; symbol_name.store(coff, &entry.name); entry.section_number = @enumFromInt(@intFromEnum(sym.section_number)); @@ -3056,8 +3080,31 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry); - for (1..num_aux_symbols + 1) |aux_index| - @memset(coff.symbolTableEntryStoragePtr(@intCast(old_num_symbols + aux_index)), 0); + if (num_aux_symbols > 0) aux_init: { + if (sym.gmi == .none) switch (coff.getNode(sym.ni)) { + .image_section => |sec_si| { + assert(si == sec_si); + const header = sym.section_number.header(coff); + const aux_ptr = coff.symbolTableSectionAuxEntryPtr(si).?; + aux_ptr.* = .{ + .length = @intCast(sym.ni.location(&coff.mf).resolve(&coff.mf)[1]), + .number_of_relocations = header.number_of_relocations, + .number_of_linenumbers = header.number_of_linenumbers, + .checksum = 0, + .number = 0, + .selection = .NONE, + .unused = @splat(0), + }; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, .@"2", aux_ptr); + + break :aux_init; + }, + else => {}, + }; + + unreachable; + } break :entry entry; }; @@ -3073,7 +3120,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }, }); - log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.extra.sti }); + log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.value.sti }); } fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void { @@ -3478,8 +3525,8 @@ pub fn addReloc( else => |loc_sn| sri: { // The target may not have a node yet, or it could be an extern that will never // have a node. In that case, flushGlobal will create the symbol table entry. - const sti: SymbolTable.Index = if (target.extra.sti != .none) - target.extra.sti + const sti: SymbolTable.Index = if (target.value.sti != .none) + target.value.sti else if (target.ni != .none) sti: { try coff.pendingSymbolTableEntry(target_si); break :sti .none; @@ -3503,14 +3550,9 @@ pub fn addReloc( try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); } - coff.targetStore( - &header.number_of_relocations, - new_num_relocations, - ); - coff.targetStore( - &coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff)).number_of_relocations, - new_num_relocations, - ); + coff.targetStore(&header.number_of_relocations, new_num_relocations); + if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff))) |aux_ptr| + coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations); // TODO: These need to allocate from a free list (once deleting relocs is supported) (or can we just remove swap?) const sri: Section.RelocationIndex = .wrap(old_num_relocations); @@ -3643,7 +3685,7 @@ fn loadObject( log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) }); - const header = try r.peekStruct(std.coff.Header, .little()); + const header = try r.peekStruct(std.coff.Header, .little); if (header.machine != target.toCoffMachine()) return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{ target.toCoffMachine(), @@ -3678,7 +3720,7 @@ fn loadObject( const string_table_len = try r.peekInt(u32, target_endian); if (string_table_len < @sizeOf(u32) or symbol_table_end + string_table_len > fl.size) - return diags.failParse(path, "bad string table", .{}); + return diags.failParse(path, "bad string table length: 0x{x}", .{string_table_len}); const ioi: InputObject.Index = @enumFromInt(coff.input_objects.items.len); try coff.input_objects.ensureUnusedCapacity(gpa, 1); @@ -4519,17 +4561,18 @@ fn loadObject( const sym = symbol.si.get(coff); assert(sym.ni == .none); sym.ni = section.si.get(coff).ni; - sym.setValue(switch (symbol.value) { - .section => |v| .{ .size = v }, - .static => |v| .{ .node_offset = v }, + switch (symbol.value) { + .section => |v| sym.setExtra(.{ .size = v }), + .static => |v| sym.setValue(.{ .node_offset = v }), .external => |v| switch (symbol.section_number) { .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable, - else => .{ .node_offset = v }, + else => sym.setValue(.{ .node_offset = v }), }, .weak_external, .weak_external_aux, => unreachable, - }); + } + sym.section_number = section.si.get(coff).section_number; } } @@ -4572,7 +4615,7 @@ fn loadObject( symbol.si = global_gop.value_ptr.*; if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { const sym = symbol.si.get(coff); - sym.setValue(.{ .size = @max(sym.size(), size) }); + sym.setExtra(.{ .size = @max(sym.size(), size) }); } }, else => unreachable, @@ -5156,12 +5199,12 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).value.size = @intCast(nw.interface.end); + si.get(coff).extra.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } if (nav.resolved.?.@"linksection".unwrap()) |_| { - try ni.resize(&coff.mf, gpa, si.get(coff).value.size); + try ni.resize(&coff.mf, gpa, si.get(coff).extra.size); var parent_ni = ni; while (true) { parent_ni = parent_ni.parent(&coff.mf); @@ -5289,7 +5332,7 @@ fn updateFuncInner( error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).value.size = @intCast(nw.interface.end); + si.get(coff).extra.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } @@ -5533,6 +5576,7 @@ pub fn flush( ) !void { _ = arena; _ = prog_node; + const comp = coff.base.comp; // TODO: When https://github.com/ziglang/zig/issues/23617 is in, // this should be set after updateExports instead @@ -5541,11 +5585,30 @@ pub fn flush( while (try coff.resolve(tid)) {} while (try coff.idle(tid)) {} + // This has to occur after all other flushMoved / flushResized have resolved, + // but it will also generate one more set of resizes and moves. + if (coff.symbol_table.pending_shrink) { + coff.symbol_table.pending_shrink = false; + + const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); + coff.symbol_table.ni.shrink( + &coff.mf, + comp.gpa, + number_of_symbols * std.coff.Symbol.sizeOf(), + true, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return comp.link_diags.fail( + "linker failed to compact symbol table: {t}", + .{e}, + ), + }; + } + while (try coff.idle(tid)) {} + if (coff.isImage()) try coff.reportUndefs(tid); - const comp = coff.base.comp; - // Implib generation should instead be done via building a MappedFile progressively if (comp.emit_implib) |implib_file| coff.flushImplib(implib_file) catch |err| @@ -5717,31 +5780,6 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; } - if (coff.symbol_table.pending_shrink) { - defer coff.symbol_table.pending_shrink = false; - const sub_prog_node = coff.idleProgNode( - tid, - coff.symbol_prog_node, - coff.getNode(coff.symbol_table.ni), - ); - defer sub_prog_node.end(); - - const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); - coff.symbol_table.ni.shrink( - &coff.mf, - comp.gpa, - number_of_symbols * std.coff.Symbol.sizeOf(), - true, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return comp.link_diags.fail( - "linker failed to compact symbol table: {t}", - .{e}, - ), - }; - - break :task; - } } if (coff.section_merge_pending_index < coff.section_merges.count()) return true; @@ -5753,7 +5791,6 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { 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.symbol_table.pending_shrink) return true; return false; } @@ -5922,7 +5959,7 @@ fn flushUav( error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).value.size = @intCast(nw.interface.end); + si.get(coff).extra.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } @@ -6358,7 +6395,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }); @memcpy(ni.slice(&coff.mf)[0..init.len], &init); sym.ni = ni; - sym.setValue(.{ .size = init.len }); + sym.extra.size = init.len; try coff.addReloc( si, init.len - 4, @@ -6539,7 +6576,7 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { error.WriteFailed => return nw.err.?, else => |e| return e, }; - si.get(coff).value.size = @intCast(nw.interface.end); + si.get(coff).extra.size = @intCast(nw.interface.end); si.applyLocationRelocs(coff); } @@ -6556,13 +6593,15 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .section_table, .placeholder, => assert(!coff.isImage()), - .symbol_table => { - coff.targetStore( - &coff.headerPtr().pointer_to_symbol_table, - @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]), - ); - }, - .string_table => { + .symbol_table, + .string_table, + => |_, tag| { + if (tag == .symbol_table) + coff.targetStore( + &coff.headerPtr().pointer_to_symbol_table, + @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]), + ); + if (!coff.symbol_table.pending_shrink) { const symbol_table_loc, const symbol_table_size = coff.symbol_table.ni.location(&coff.mf).resolve(&coff.mf); const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf); @@ -6822,10 +6861,8 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { } if (!coff.isImage()) { - coff.targetStore( - &coff.symbolTableSectionAuxEntryPtr(si).length, - @intCast(size), - ); + if (coff.symbolTableSectionAuxEntryPtr(si)) |aux_ptr| + coff.targetStore(&aux_ptr.length, @intCast(size)); } }, .input_section => {}, @@ -6853,7 +6890,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ); } - smi.symbol(coff).get(coff).value.size = @intCast(size); + smi.symbol(coff).get(coff).extra.size = @intCast(size); }, .import_thunk, .nav, @@ -7091,14 +7128,16 @@ fn updateExportsInner( const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; - export_sym.setValue(.{ .size = exported_sym.value.size }); export_sym.section_number = exported_sym.section_number; defer export_si.applyTargetRelocs(coff, .none); const prev_alias_sym = prev_alias_si.get(coff); - assert(!prev_alias_sym.flags.has_alias); - prev_alias_sym.extra = .{ .next_alias_si = export_si }; - prev_alias_sym.flags.has_alias = true; + switch (prev_alias_sym.flags.extra_tag) { + .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }), + else => unreachable, + } + + prev_alias_sym.setExtra(.{ .next_alias_si = export_si }); prev_alias_si = export_si; if (!coff.isImage()) continue; @@ -7237,7 +7276,7 @@ fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) !voi 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, + if (sym.flags.extra_tag == .size) sym.extra.size else 0, sym.ni, name.toSlice(coff), }); @@ -7251,11 +7290,11 @@ fn printSymbol( ) !void { const sym = si.get(coff); const node = coff.getNode(sym.ni); - try w.print("{d:0>6}@{d:0>2} {x:08} {s} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} ", .{ + try w.print("{d:0>6}@{d:0>2} {x:08} {s} {s} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} ", .{ si, sym.section_number, - if (sym.flags.value_tag == .size) - @as(u64, sym.value.size) + if (sym.flags.extra_tag == .size) + @as(u64, sym.extra.size) else if (sym.ni != .none) sym.ni.location(&coff.mf).resolve(&coff.mf)[1] else @@ -7264,7 +7303,12 @@ fn printSymbol( .weak_alias_name => "an", .weak_alias_si => "as", .node_offset => "no", + .sti => "st", + }, + switch (sym.flags.extra_tag) { .size => "sz", + .isli => "li", + .next_alias_si => "na", }, switch (sym.flags.type) { .unknown => "u", -- 2.54.0 From de297890e399b83fe99182cc2f223117099f35b2 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 64/94] test/link: add emit-static-lib --- test/link.zig | 40 ++++++++++++++++++++- test/link/snapshots/emit-static-lib.lib.dmp | 9 +++++ test/src/Link.zig | 15 ++++++-- 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 test/link/snapshots/emit-static-lib.lib.dmp diff --git a/test/link.zig b/test/link.zig index 65ec8f4e7e2ea7f5f7b7bed6de646ed1a5b9a329..3faecc710feecb15376718833d151a4708b58d3b 100644 --- a/test/link.zig +++ b/test/link.zig @@ -1,4 +1,4 @@ -pub fn addCases(ctx: *@import("tests.zig").LinkContext) void { +pub fn addCases(ctx: *LinkContext) void { if (ctx.includeTest("exports-static")) |prefix| { const lib = ctx.addLibrary(.static, .{ .name = "lib", @@ -23,8 +23,46 @@ pub fn addCases(ctx: *@import("tests.zig").LinkContext) void { }, .{}); } + if (ctx.includeTest("emit-static-lib")) |prefix| { + const obj1 = ctx.addObject(.{ + .name = "obj1", + .use_llvm = true, + .use_lld = true, + .c_source_bytes = + \\int foo1 = 1; + \\int foo2 = 2; + \\int fooBar() { + \\ return foo1 + foo2; + \\} + , + }); + const obj2 = ctx.addObject(.{ + .name = "this_is_a_long_name", + .zig_source_bytes = + \\fn weakFoo() callconv(.c) usize { + \\ return 42; + \\} + \\export var strong_foo: usize = 100; + \\comptime { + \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak }); + \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong }); + \\} + , + }); + const lib = ctx.addLibrary(.static, .{ .name = "lib" }); + lib.root_module.addObject(obj1); + lib.root_module.addObject(obj2); + ctx.verifyObjdump(prefix, lib, &.{ + "-s", + "--elements=file-type", + "--symbols", + "--only-symbol=foo", + "--only-symbol=Foo", + }, .{}); + } } +const LinkContext = @import("tests.zig").LinkContext; const std = @import("std"); diff --git a/test/link/snapshots/emit-static-lib.lib.dmp b/test/link/snapshots/emit-static-lib.lib.dmp new file mode 100644 index 0000000000000000000000000000000000000000..7f187063d02f8bb44c5574b0bf94ade24576c525 --- /dev/null +++ b/test/link/snapshots/emit-static-lib.lib.dmp @@ -0,0 +1,9 @@ +lib.lib: COFF archive +lib.lib(obj1.obj): COFF object +xxxx 00000000 1 NULL() EXTERNAL | fooBar +xxxx 00000000 2 NULL EXTERNAL | foo1 +xxxx 00000004 2 NULL EXTERNAL | foo2 +lib.lib(this_is_a_long_name.obj): COFF object +xxxx 00000000 4 NULL() EXTERNAL | weakFoo +xxxx 00000000 2 NULL EXTERNAL | strong_foo_alias +xxxx 00000000 2 NULL EXTERNAL | strong_foo diff --git a/test/src/Link.zig b/test/src/Link.zig index adcc329b6a98e54667002b3236dde144e39ba752..08ad3f228fbe2dd57fe9fe4fbaf0a7b80794184f 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -30,8 +30,17 @@ pub fn addLibrary( .linkage = linkage, .name = overlay.name, .root_module = self.createModule(overlay), - .use_llvm = self.use_llvm, - .use_lld = self.use_lld, + .use_llvm = overlay.use_llvm orelse self.use_llvm, + .use_lld = overlay.use_lld orelse self.use_lld, + }); +} + +pub fn addObject(self: *const Link, overlay: OverlayOptions) *Step.Compile { + return self.b.addObject(.{ + .name = overlay.name, + .root_module = self.createModule(overlay), + .use_llvm = overlay.use_llvm orelse self.use_llvm, + .use_lld = overlay.use_lld orelse self.use_lld, }); } @@ -169,6 +178,8 @@ const OverlayOptions = struct { zig_source_file: ?std.Build.LazyPath = null, pic: ?bool = null, strip: ?bool = null, + use_llvm: ?bool = null, + use_lld: ?bool = null, }; const std = @import("std"); -- 2.54.0 From b0254a287736922cfb7204650c8e0104aad3ecac Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:36 -0400 Subject: [PATCH 65/94] Coff: fixup updating sizes of aliased symbols --- src/link/Coff.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bdd7aa56c837dc562d051d4433b60681315459b9..23a2326899993bc8c61e4d1f9cf211781909698b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -6890,7 +6890,11 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { ); } - smi.symbol(coff).get(coff).extra.size = @intCast(size); + var sym = smi.symbol(coff).get(coff); + while (sym.flags.extra_tag == .next_alias_si) + sym = sym.extra.next_alias_si.get(coff); + + sym.extra.size = @intCast(size); }, .import_thunk, .nav, -- 2.54.0 From e20860765e926dfab43492c3e0fbbe09f08818f6 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 66/94] test/link: rework test case names to show the target Prior to this, it was hard to track down commands to reproduce failing tests in --verbose output --- test/link.zig | 56 +++- ...static-lib.lib.dmp => emit-static-lib.dmp} | 0 ...ts-dynamic.lib.dmp => exports-dynamic.dmp} | 0 ...orts-static.lib.dmp => exports-static.dmp} | 0 test/src/Link.zig | 243 +++++++++++------- test/tests.zig | 14 +- 6 files changed, 203 insertions(+), 110 deletions(-) rename test/link/snapshots/{emit-static-lib.lib.dmp => emit-static-lib.dmp} (100%) rename test/link/snapshots/{exports-dynamic.lib.dmp => exports-dynamic.dmp} (100%) rename test/link/snapshots/{exports-static.lib.dmp => exports-static.dmp} (100%) diff --git a/test/link.zig b/test/link.zig index 3faecc710feecb15376718833d151a4708b58d3b..a414f3847c4447abcb7f08b28231c8615a396319 100644 --- a/test/link.zig +++ b/test/link.zig @@ -1,48 +1,52 @@ pub fn addCases(ctx: *LinkContext) void { - if (ctx.includeTest("exports-static")) |prefix| { - const lib = ctx.addLibrary(.static, .{ + if (ctx.includeTest("exports-static")) |case| { + const lib = case.addLibrary(.static, .{ .name = "lib", .zig_source_file = ctx.sourcePath("exports.zig"), }); - ctx.verifyObjdump(prefix, lib, &.{ + case.verifyObjdump(lib, &.{ "-s", "--symbols", "--only-symbol=foo", }, .{}); } - if (ctx.includeTest("exports-dynamic")) |prefix| { - const lib = ctx.addLibrary(.dynamic, .{ + if (ctx.includeTest("exports-dynamic")) |case| { + const lib = case.addLibrary(.dynamic, .{ .name = "lib", .zig_source_file = ctx.sourcePath("exports.zig"), }); - ctx.verifyObjdump(prefix, lib, &.{ + case.verifyObjdump(lib, &.{ "-s", "--exports", "--only-symbol=foo", }, .{}); } - if (ctx.includeTest("emit-static-lib")) |prefix| { - const obj1 = ctx.addObject(.{ + if (ctx.includeTest("emit-static-lib")) |case| { + const obj1 = case.addObject(.{ .name = "obj1", + .name_prefix = false, + .name_target = false, .use_llvm = true, .use_lld = true, .c_source_bytes = \\int foo1 = 1; \\int foo2 = 2; - \\int fooBar() { + \\unsigned int fooBar() { \\ return foo1 + foo2; \\} , }); - const obj2 = ctx.addObject(.{ + const obj2 = case.addObject(.{ .name = "this_is_a_long_name", + .name_prefix = false, + .name_target = false, .zig_source_bytes = \\fn weakFoo() callconv(.c) usize { - \\ return 42; + \\ return 0xaabbccdd; \\} - \\export var strong_foo: usize = 100; + \\export var strong_foo: usize = 0x11223344; \\comptime { \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak }); \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong }); @@ -50,17 +54,41 @@ pub fn addCases(ctx: *LinkContext) void { , }); - const lib = ctx.addLibrary(.static, .{ .name = "lib" }); + const lib = case.addLibrary(.static, .{ + .name = "lib", + .name_prefix = false, + .name_target = false, + }); lib.root_module.addObject(obj1); lib.root_module.addObject(obj2); - ctx.verifyObjdump(prefix, lib, &.{ + case.verifyObjdump(lib, &.{ "-s", "--elements=file-type", "--symbols", "--only-symbol=foo", "--only-symbol=Foo", }, .{}); + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = + \\extern fn fooBar() c_uint; + \\extern fn weakFoo() usize; + \\extern var strong_foo: usize; + \\extern var strong_foo_alias: usize; + \\pub fn main() !u8 { + \\ return @intFromBool(0xcd003368 != fooBar() + + \\ weakFoo() + + \\ strong_foo + + \\ strong_foo_alias); + \\} + , + }); + exe.root_module.linkLibrary(lib); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 0 } }); } } diff --git a/test/link/snapshots/emit-static-lib.lib.dmp b/test/link/snapshots/emit-static-lib.dmp similarity index 100% rename from test/link/snapshots/emit-static-lib.lib.dmp rename to test/link/snapshots/emit-static-lib.dmp diff --git a/test/link/snapshots/exports-dynamic.lib.dmp b/test/link/snapshots/exports-dynamic.dmp similarity index 100% rename from test/link/snapshots/exports-dynamic.lib.dmp rename to test/link/snapshots/exports-dynamic.dmp diff --git a/test/link/snapshots/exports-static.lib.dmp b/test/link/snapshots/exports-static.dmp similarity index 100% rename from test/link/snapshots/exports-static.lib.dmp rename to test/link/snapshots/exports-static.dmp diff --git a/test/src/Link.zig b/test/src/Link.zig index 08ad3f228fbe2dd57fe9fe4fbaf0a7b80794184f..9982e50e12d3b77b485db3cc1066db3a27689001 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -2,6 +2,7 @@ b: *Build, step: *Step, optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget, +target_desc: []const u8, use_llvm: bool, use_lld: bool, link_libc: bool, @@ -10,111 +11,168 @@ update_step: ?*Step.UpdateSourceFiles, updated_snapshots: std.StringArrayHashMapUnmanaged(void), max_rss: usize, -pub fn includeTest(self: *const Link, prefix: []const u8) ?[]const u8 { +pub fn includeTest(self: *Link, prefix: []const u8) ?Case { if (for (self.test_filters) |filter| { if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false; } else self.test_filters.len > 0) return null; - return prefix; + + return .{ + .ctx = self, + .prefix = prefix, + }; } pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath { return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path })); } -pub fn addLibrary( - self: *const Link, - linkage: std.builtin.LinkMode, - overlay: OverlayOptions, -) *Step.Compile { - return self.b.addLibrary(.{ - .linkage = linkage, - .name = overlay.name, - .root_module = self.createModule(overlay), - .use_llvm = overlay.use_llvm orelse self.use_llvm, - .use_lld = overlay.use_lld orelse self.use_lld, - }); -} - -pub fn addObject(self: *const Link, overlay: OverlayOptions) *Step.Compile { - return self.b.addObject(.{ - .name = overlay.name, - .root_module = self.createModule(overlay), - .use_llvm = overlay.use_llvm orelse self.use_llvm, - .use_lld = overlay.use_lld orelse self.use_lld, - }); -} - -const SnapshotScope = packed struct { - arch: bool = false, - os: bool = false, - abi: bool = false, - optimize: bool = false, - use_llvm: bool = false, - use_lld: bool = false, - link_libc: bool = false, -}; - -/// Verify the results of a `zig objdump` call against a snapshot, which -/// contains the expected output. Snapshots alias between all build -/// configurations by default, but by specifying fields in `scope`, -/// unique snapshot names are generated for each value of that field. -pub fn verifyObjdump( - self: *Link, +pub const Case = struct { + ctx: *Link, prefix: []const u8, - compile: *Step.Compile, - args: []const []const u8, - scope: SnapshotScope, -) void { - const snapshot_name = self.snapshotName(prefix, compile.name, scope) catch @panic("OOM"); - const snapshot_sub_path = self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name }); - - // Many tests may read the same snapshot, so only use the first one to update. - // If there are differences in output, they will show up on the next test run. - if (self.update_step != null) { - const gop = self.updated_snapshots.getOrPut(self.b.allocator, snapshot_sub_path) catch @panic("OOM"); - if (gop.found_existing) return; + + fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 { + if (!overlay.name_prefix and !overlay.name_target) + return overlay.name; + + if (overlay.name_prefix == overlay.name_target) + return self.ctx.b.fmt("{s}-{s}-{s}", .{ self.prefix, overlay.name, self.ctx.target_desc }) + else if (overlay.name_prefix) + return self.ctx.b.fmt("{s}-{s}", .{ self.prefix, overlay.name }) + else + return self.ctx.b.fmt("{s}-{s}", .{ overlay.name, self.ctx.target_desc }); + } + + pub fn addLibrary( + self: *const Case, + linkage: std.builtin.LinkMode, + overlay: OverlayOptions, + ) *Step.Compile { + return self.ctx.b.addLibrary(.{ + .linkage = linkage, + .name = self.resolveName(&overlay), + .root_module = self.ctx.createModule(overlay), + .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm, + .use_lld = overlay.use_lld orelse self.ctx.use_lld, + }); + } + + pub fn addExecutable( + self: *const Case, + overlay: OverlayOptions, + ) *Step.Compile { + return self.ctx.b.addExecutable(.{ + .name = self.resolveName(&overlay), + .root_module = self.ctx.createModule(overlay), + .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm, + .use_lld = overlay.use_lld orelse self.ctx.use_lld, + }); + } + + pub fn addRunArtifact( + self: *const Case, + exe: *Step.Compile, + ) *Step.Run { + const run_step = self.ctx.b.addRunArtifact(exe); + run_step.skip_foreign_checks = true; + self.ctx.step.dependOn(&run_step.step); + return run_step; + } + + pub fn addObject( + self: *const Case, + overlay: OverlayOptions, + ) *Step.Compile { + return self.ctx.b.addObject(.{ + .name = self.resolveName(&overlay), + .root_module = self.ctx.createModule(overlay), + .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm, + .use_lld = overlay.use_lld orelse self.ctx.use_lld, + }); } - const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name})); - run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" }); - run_step.addArtifactArg(compile); - run_step.addArgs(args); - run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); + const SnapshotScope = struct { + /// If a test case has multiple verifyObjdump calls, `opt_sub_name` can + /// be used to differentiate them. + sub_name: ?[]const u8 = null, + arch: bool = false, + os: bool = false, + abi: bool = false, + optimize: bool = false, + use_llvm: bool = false, + use_lld: bool = false, + link_libc: bool = false, + }; + + /// Verify the results of a `zig objdump` call against a snapshot, which + /// contains the expected output. Snapshots alias between all build + /// configurations by default, but by specifying fields in `scope`, + /// unique snapshot names are generated for each value of that field. + /// + pub fn verifyObjdump( + self: *const Case, + compile: *Step.Compile, + args: []const []const u8, + scope: SnapshotScope, + ) void { + const ctx = self.ctx; + const snapshot_name = self.snapshotName(scope) catch @panic("OOM"); + const snapshot_sub_path = ctx.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name }); + + // Many tests may read the same snapshot, so only use the first one to update. + // If there are differences in output, they will show up on the next test run. + if (ctx.update_step != null) { + const gop = ctx.updated_snapshots.getOrPut(ctx.b.allocator, snapshot_sub_path) catch @panic("OOM"); + if (gop.found_existing) return; + } + + const run_step = Step.Run.create(ctx.b, ctx.b.fmt( + "objdump {s} {s}", + .{ snapshot_name, ctx.target_desc }, + )); + run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" }); + run_step.addArtifactArg(compile); + run_step.addArgs(args); + run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); + + if (ctx.update_step) |update_step| { + // Workaround for the build system not realizing objdump itself has changed + run_step.has_side_effects = true; - if (self.update_step) |update_step| { - // Workaround for the build system not realizing objdump itself has changed - run_step.has_side_effects = true; + const snapshot_update_path = run_step.captureStdOut(.{}); + update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path); + } else { + run_step.addCheck(.{ .snapshot = .{ .file = ctx.b.path(snapshot_sub_path) } }); + } - const snapshot_update_path = run_step.captureStdOut(.{}); - update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path); - } else { - run_step.addCheck(.{ .snapshot = .{ .file = self.b.path(snapshot_sub_path) } }); + ctx.step.dependOn(&run_step.step); } - self.step.dependOn(&run_step.step); -} - -fn snapshotName( - self: *const Link, - test_name: []const u8, - compile_name: []const u8, - scope: SnapshotScope, -) ![]const u8 { - var snapshot_name: std.Io.Writer.Allocating = .init(self.b.allocator); - const w = &snapshot_name.writer; - - try w.print("{s}.{s}", .{ test_name, compile_name }); - if (scope.arch) try w.print("-{t}", .{self.target.result.cpu.arch}); - if (scope.os) try w.print("-{t}", .{self.target.result.os.tag}); - if (scope.abi) try w.print("-{t}", .{self.target.result.abi}); - if (scope.optimize) try w.print("-{t}", .{self.optimize}); - if (scope.use_llvm) try w.writeAll(if (self.use_llvm) "-llvm" else "-no-llvm"); - if (scope.use_lld) try w.writeAll(if (self.use_lld) "-lld" else "-no-lld"); - if (scope.link_libc) try w.writeAll(if (self.link_libc) "-libc" else "-no-libc"); - try w.writeAll(".dmp"); - - return try snapshot_name.toOwnedSlice(); -} + fn snapshotName( + self: *const Case, + scope: SnapshotScope, + ) ![]const u8 { + const ctx = self.ctx; + var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator); + const w = &snapshot_name.writer; + + try w.writeAll(self.prefix); + if (scope.sub_name) |sub_name| { + try w.writeByte('.'); + try w.writeAll(sub_name); + } + + if (scope.arch) try w.print("-{t}", .{ctx.target.result.cpu.arch}); + if (scope.os) try w.print("-{t}", .{ctx.target.result.os.tag}); + if (scope.abi) try w.print("-{t}", .{ctx.target.result.abi}); + if (scope.optimize) try w.print("-{t}", .{ctx.optimize}); + if (scope.use_llvm) try w.writeAll(if (ctx.use_llvm) "-llvm" else "-no-llvm"); + if (scope.use_lld) try w.writeAll(if (ctx.use_lld) "-lld" else "-no-lld"); + if (scope.link_libc) try w.writeAll(if (ctx.link_libc) "-libc" else "-no-libc"); + try w.writeAll(".dmp"); + + return try snapshot_name.toOwnedSlice(); + } +}; fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { const write_files = self.b.addWriteFiles(); @@ -165,6 +223,13 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { const OverlayOptions = struct { name: []const u8, + /// Prefix the name with the test case prefix. + /// Unset if names with specific lengths are needed. + name_prefix: bool = true, + /// Prefix the name with `target_desc`. + /// Can be unset when the snapshot needs to contain the name, + /// so that snapshots can alias between targets. + name_target: bool = true, asm_source_bytes: ?[]const u8 = null, c_source_bytes: ?[]const u8 = null, c_source_flags: []const []const u8 = &.{}, diff --git a/test/tests.zig b/test/tests.zig index c3620b0bf0672140d7935b2f8b49802d36ee8e40..80366c7f5c7937f3c07b40ac90611c72ec2bf890 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3184,13 +3184,13 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step { .step = step, .optimize = optimize_mode, .target = resolved_target, - // .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{ - // target.zigTriple(b.allocator) catch @panic("OOM"), - // optimize_mode, - // if (link_target.use_llvm) "-llvm" else "", - // if (link_target.use_lld) "-lld" else "", - // if (link_target.link_libc) "-libc" else "", - // }) catch @panic("OOM"), + .target_desc = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{ + target.zigTriple(b.allocator) catch @panic("OOM"), + optimize_mode, + if (link_target.use_llvm) "-llvm" else "", + if (link_target.use_lld) "-lld" else "", + if (link_target.link_libc) "-libc" else "", + }) catch @panic("OOM"), .use_llvm = link_target.use_llvm, .use_lld = link_target.use_lld, .link_libc = link_target.link_libc, -- 2.54.0 From c14bc1bbe09069a2371085e3a1275e30f4665c6b Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 67/94] Coff: fixup alignment of second linker member accessors test/link: update emit-static-lib test to check array relocs --- src/link/Coff.zig | 12 +++++------- test/link.zig | 9 ++++++--- test/link/snapshots/emit-static-lib.dmp | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 23a2326899993bc8c61e4d1f9cf211781909698b..5a62f3e016ec28e37cbb65073bac2f7cdcdb5576 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -2404,19 +2404,19 @@ pub fn firstLinkerMemberOffsetsSlice(coff: *Coff) []u32 { return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. len * @sizeOf(u32)])); } -pub fn secondLinkerMemberNumMembersPtr(coff: *Coff) *u32 { +pub fn secondLinkerMemberNumMembersPtr(coff: *Coff) *align(2) u32 { assert(coff.isArchive()); return @ptrCast(@alignCast(Node.known.second_linker_member.slice(&coff.mf))); } -pub fn secondLinkerMemberOffsetsSlice(coff: *Coff) []u32 { +pub fn secondLinkerMemberOffsetsSlice(coff: *Coff) []align(2) u32 { const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); return @ptrCast(@alignCast( Node.known.second_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. num_members * @sizeOf(u32)], )); } -pub fn secondLinkerMemberNumSymbolsPtr(coff: *Coff) *u32 { +pub fn secondLinkerMemberNumSymbolsPtr(coff: *Coff) *align(2) u32 { const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr()); return @ptrCast(@alignCast( Node.known.second_linker_member.slice(&coff.mf)[(1 + num_members) * @sizeOf(u32) ..], @@ -2970,9 +2970,7 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void { try coff.lib_string_table.append(gpa, name); const slice = Node.known.second_linker_member.slice(&coff.mf); - const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..])); - coff.targetStore(num_symbols_ptr, @intFromEnum(mfli) + 1); - + coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), @intFromEnum(mfli) + 1); if (!needs_sort) { @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]); @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]); @@ -4670,7 +4668,7 @@ fn loadObject( if (include_section) { assert(coff.getNode(symbol.si.get(coff).ni) == .input_section); - symbol.si.get(coff).extra = .{ .isli = @enumFromInt(coff.input_symbols.items.len) }; + symbol.si.get(coff).setExtra(.{ .isli = @enumFromInt(coff.input_symbols.items.len) }); coff.input_symbols.addOneAssumeCapacity().* = .{ .si = symbol.si, .name = symbol.name, diff --git a/test/link.zig b/test/link.zig index a414f3847c4447abcb7f08b28231c8615a396319..1d0a799e4f46e4d3f00aa6639667d830b1454b69 100644 --- a/test/link.zig +++ b/test/link.zig @@ -44,9 +44,10 @@ pub fn addCases(ctx: *LinkContext) void { .name_target = false, .zig_source_bytes = \\fn weakFoo() callconv(.c) usize { - \\ return 0xaabbccdd; + \\ return 0xaabbccddaabbccdd; \\} - \\export var strong_foo: usize = 0x11223344; + \\export var array_foo: [2]u16 = .{ 0xffff, 0xabcd }; + \\export var strong_foo: usize = 0x1122334411223344; \\comptime { \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak }); \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong }); @@ -75,11 +76,13 @@ pub fn addCases(ctx: *LinkContext) void { .zig_source_bytes = \\extern fn fooBar() c_uint; \\extern fn weakFoo() usize; + \\extern var array_foo: [2]u16; \\extern var strong_foo: usize; \\extern var strong_foo_alias: usize; \\pub fn main() !u8 { - \\ return @intFromBool(0xcd003368 != fooBar() + + \\ return @intFromBool(0xcd003365cd00df35 != fooBar() + \\ weakFoo() + + \\ array_foo[1] + \\ strong_foo + \\ strong_foo_alias); \\} diff --git a/test/link/snapshots/emit-static-lib.dmp b/test/link/snapshots/emit-static-lib.dmp index 7f187063d02f8bb44c5574b0bf94ade24576c525..41932df9785ea10382d73473f93e8e4b16a3fe72 100644 --- a/test/link/snapshots/emit-static-lib.dmp +++ b/test/link/snapshots/emit-static-lib.dmp @@ -5,5 +5,6 @@ xxxx 00000000 2 NULL EXTERNAL | foo1 xxxx 00000004 2 NULL EXTERNAL | foo2 lib.lib(this_is_a_long_name.obj): COFF object xxxx 00000000 4 NULL() EXTERNAL | weakFoo +xxxx 00000010 2 NULL EXTERNAL | array_foo xxxx 00000000 2 NULL EXTERNAL | strong_foo_alias xxxx 00000000 2 NULL EXTERNAL | strong_foo -- 2.54.0 From 344d0ab72c049035cacdca63094c07cd512f89cd Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 68/94] Coff: handle relocations of absolute symbols test/link: add absolute symbol tests --- lib/compiler/objdump.zig | 11 +- src/link/Coff.zig | 360 +++++++++++++--------- test/link.zig | 59 ++++ test/link/snapshots/abs-symbol-x86_64.dmp | 1 + test/src/Link.zig | 10 + 5 files changed, 292 insertions(+), 149 deletions(-) create mode 100644 test/link/snapshots/abs-symbol-x86_64.dmp diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 0202998db237c83ceef7ebd07159c76a442cc438..dc76d6da0d9c1f363bef8636d0b0a644eb75cb85 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -1069,11 +1069,12 @@ const coff = struct { for (sections.items, 0..) |section, section_i| { if (section.header.pointer_to_relocations == 0) continue; - try w.print( - \\Relocs for section {x} '{s}' in {s}: - \\ Offset Type Symbol -> Sect Name - \\ - , .{ section_i + 1, section.name, obj_name }); + if (d.element(.@"table-header")) + try w.print( + \\Relocs for section {x} '{s}' in {s}: + \\ Offset Type Symbol -> Sect Name + \\ + , .{ section_i + 1, section.name, obj_name }); fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err| return d.failParse("unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err }); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 5a62f3e016ec28e37cbb65073bac2f7cdcdb5576..6249cc30890dcc6e4efe8468421df562964e4fdf 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1051,11 +1051,11 @@ pub const Symbol = struct { }; } - pub fn flushMoved(si: Symbol.Index, coff: *Coff) void { + pub fn flushMoved(si: Symbol.Index, coff: *Coff) !void { const sym = si.get(coff); sym.rva = coff.computeNodeRva(sym.ni) + sym.nodeOffset(coff); - si.applyLocationRelocs(coff); - si.applyTargetRelocs(coff, .none); + try si.applyLocationRelocs(coff); + try si.applyTargetRelocs(coff, .none); var alias_sym = sym; while (alias_sym.flags.extra_tag == .next_alias_si) { @@ -1063,7 +1063,7 @@ pub const Symbol = struct { alias_sym = alias_si.get(coff); assert(alias_sym.ni == sym.ni); alias_sym.rva = sym.rva; - alias_si.applyTargetRelocs(coff, .none); + try alias_si.applyTargetRelocs(coff, .none); } } @@ -1080,7 +1080,7 @@ pub const Symbol = struct { } } - pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) void { + pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) !void { const sym = si.get(coff); switch (sym.loc_relocs) { .none => {}, @@ -1091,20 +1091,20 @@ pub const Symbol = struct { &entry.virtual_address, @intCast(coff.computeSymbolSectionOffset(sym) + reloc.offset), ); - reloc.apply(coff); + try reloc.apply(coff); } }, } } - pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) void { + pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) !void { const sym = si.get(coff); var ri = sym.target_relocs; while (ri != end) { const reloc = ri.get(coff); assert(reloc.target == si); - reloc.apply(coff); + try reloc.apply(coff); ri = reloc.next; } } @@ -1166,7 +1166,7 @@ pub const Reloc = extern struct { } }; - pub fn apply(reloc: *Reloc, coff: *Coff) void { + pub fn apply(reloc: *Reloc, coff: *Coff) !void { const loc_sym = reloc.loc.get(coff); switch (loc_sym.ni) { .none => return, @@ -1300,118 +1300,163 @@ pub const Reloc = extern struct { } const target_sym = reloc.target.get(coff); - switch (target_sym.ni) { - .none => return, - else => |ni| if (ni.hasMoved(&coff.mf)) return, - } + const is_abs = switch (target_sym.ni) { + .none => if (target_sym.section_number == .ABSOLUTE) true else return, + else => |ni| if (ni.hasMoved(&coff.mf)) return else false, + }; const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend)); - switch (target_machine) { - else => |machine| @panic(@tagName(machine)), - .AMD64 => switch (reloc.type.AMD64) { - else => |kind| @panic(@tagName(kind)), - .ABSOLUTE => {}, - .ADDR64 => std.mem.writeInt( - u64, - loc_slice[0..8], - coff.optionalHeaderField(.image_base) + target_rva, - target_endian, - ), - .ADDR32 => std.mem.writeInt( - u32, - loc_slice[0..4], - @intCast(coff.optionalHeaderField(.image_base) + target_rva), - target_endian, - ), - .ADDR32NB => std.mem.writeInt( - u32, - loc_slice[0..4], - @intCast(target_rva), - target_endian, - ), - .REL32 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))), - target_endian, - ), - .REL32_1 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))), - target_endian, - ), - .REL32_2 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))), - target_endian, - ), - .REL32_3 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))), - target_endian, - ), - .REL32_4 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))), - target_endian, - ), - .REL32_5 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))), - target_endian, - ), - .SECREL => std.mem.writeInt( - u32, - loc_slice[0..4], - @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), - target_endian, - ), - }, - .I386 => switch (reloc.type.I386) { - else => |kind| @panic(@tagName(kind)), - .ABSOLUTE => {}, - .DIR16 => std.mem.writeInt( - u16, - loc_slice[0..2], - @intCast(coff.optionalHeaderField(.image_base) + target_rva), - target_endian, - ), - .REL16 => std.mem.writeInt( - i16, - loc_slice[0..2], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))), - target_endian, - ), - .DIR32 => std.mem.writeInt( - u32, - loc_slice[0..4], - @intCast(coff.optionalHeaderField(.image_base) + target_rva), - target_endian, - ), - .DIR32NB => std.mem.writeInt( - u32, - loc_slice[0..4], - @intCast(target_rva), - target_endian, - ), - .REL32 => std.mem.writeInt( - i32, - loc_slice[0..4], - @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))), - target_endian, - ), - .SECREL => std.mem.writeInt( - u32, - loc_slice[0..4], - @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), - target_endian, - ), - }, + if (is_abs) { + switch (target_machine) { + else => |machine| @panic(@tagName(machine)), + .AMD64 => switch (reloc.type.AMD64) { + // TODO: Report these later, in reportUndefs -> reportRelocErrs ? + else => |kind| return coff.base.comp.link_diags.fail( + "absolute symbol '{s}' targeted by invalid relocation type: {t}", + .{ target_sym.gmi.globalName(coff).name.toSlice(coff), kind }, + ), + .ABSOLUTE => {}, + .ADDR64 => std.mem.writeInt( + u64, + loc_slice[0..8], + target_rva, + target_endian, + ), + .ADDR32 => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(target_rva), + target_endian, + ), + }, + .I386 => switch (reloc.type.I386) { + else => |kind| return coff.base.comp.link_diags.fail( + "absolute symbol '{s}' targeted by invalid relocation type: {t}", + .{ target_sym.gmi.globalName(coff).name.toSlice(coff), kind }, + ), + .ABSOLUTE => {}, + .DIR16 => std.mem.writeInt( + u16, + loc_slice[0..2], + @intCast(target_rva), + target_endian, + ), + .DIR32 => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(target_rva), + target_endian, + ), + }, + } + } else { + switch (target_machine) { + else => |machine| @panic(@tagName(machine)), + .AMD64 => switch (reloc.type.AMD64) { + else => |kind| @panic(@tagName(kind)), + .ABSOLUTE => {}, + .ADDR64 => std.mem.writeInt( + u64, + loc_slice[0..8], + coff.optionalHeaderField(.image_base) + target_rva, + target_endian, + ), + .ADDR32 => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(coff.optionalHeaderField(.image_base) + target_rva), + target_endian, + ), + .ADDR32NB => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(target_rva), + target_endian, + ), + .REL32 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))), + target_endian, + ), + .REL32_1 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))), + target_endian, + ), + .REL32_2 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))), + target_endian, + ), + .REL32_3 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))), + target_endian, + ), + .REL32_4 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))), + target_endian, + ), + .REL32_5 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))), + target_endian, + ), + .SECREL => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), + target_endian, + ), + }, + .I386 => switch (reloc.type.I386) { + else => |kind| @panic(@tagName(kind)), + .ABSOLUTE => {}, + .DIR16 => std.mem.writeInt( + u16, + loc_slice[0..2], + @intCast(coff.optionalHeaderField(.image_base) + target_rva), + target_endian, + ), + .REL16 => std.mem.writeInt( + i16, + loc_slice[0..2], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))), + target_endian, + ), + .DIR32 => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(coff.optionalHeaderField(.image_base) + target_rva), + target_endian, + ), + .DIR32NB => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(target_rva), + target_endian, + ), + .REL32 => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))), + target_endian, + ), + .SECREL => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), + target_endian, + ), + }, + } } } @@ -3170,7 +3215,7 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { }); if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; - si.applyLocationRelocs(coff); + try si.applyLocationRelocs(coff); } fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { @@ -3874,9 +3919,12 @@ fn loadObject( value: union(enum) { // Size of the section section: u32, - // Offset within the section + // If section is absolute, the symbol value. + // Otherwise, offset within the section. static: u32, - // If section is undefined, the symbol size. Otherwise offset within the section. + // If section is undefined, the symbol size. + // If section is absolute, the symbol value. + // Otherwise offset within the section. external: u32, // The index of the target symbol of this weak external weak_external: u32, @@ -3945,7 +3993,10 @@ fn loadObject( .STATIC, .LABEL => |storage_class| switch (section_number) { // TODO: Do we need to do anything with @feat.00? // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392 - .UNDEFINED, .DEBUG, .ABSOLUTE => &.{}, + .UNDEFINED, + .DEBUG, + => &.{}, + .ABSOLUTE => &.{.{ .static = symbol.value }}, else => |sn| { const section = §ions[sn.toIndex()]; @@ -4049,12 +4100,9 @@ fn loadObject( ), }, .EXTERNAL => switch (section_number) { - .UNDEFINED => &.{.{ .external = symbol.value }}, - .ABSOLUTE => return diags.failParse( - path, - "TODO unhandled external absolute symbol 0x{x}: '{s}'", - .{ symbol_i, name }, - ), + .UNDEFINED, + .ABSOLUTE, + => &.{.{ .external = symbol.value }}, .DEBUG => return diags.failParse( path, "unexpected external symbol 0x{x} in DEBUG section: '{s}'", @@ -4517,7 +4565,28 @@ fn loadObject( continue; }, }, - .ABSOLUTE, .DEBUG => continue, + .ABSOLUTE => { + const value = sym: switch (symbol.value) { + .static => |value| { + symbol.si = coff.addSymbolAssumeCapacity(); + break :sym value; + }, + .external => |value| { + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + symbol.si = global_gop.value_ptr.*; + if (global_gop.found_existing) + return coff.failMultipleDefinitions(path, member_name, symbol.name, index, global_gop.value_ptr.*, .none); + break :sym value; + }, + else => unreachable, + }; + + const sym = symbol.si.get(coff); + sym.rva = value; + sym.section_number = .ABSOLUTE; + continue; + }, + .DEBUG => continue, else => |sn| §ions[sn.toIndex()], }; @@ -5198,7 +5267,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde else => |e| return e, }; si.get(coff).extra.size = @intCast(nw.interface.end); - si.applyLocationRelocs(coff); + try si.applyLocationRelocs(coff); } if (nav.resolved.?.@"linksection".unwrap()) |_| { @@ -5331,7 +5400,7 @@ fn updateFuncInner( else => |e| return e, }; si.get(coff).extra.size = @intCast(nw.interface.end); - si.applyLocationRelocs(coff); + try si.applyLocationRelocs(coff); } pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void { @@ -5433,6 +5502,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { switch (target_sym.ni) { .none => { assert(target_sym.gmi != .none); + if (target_sym.section_number == .ABSOLUTE) continue; (try undef_indices.addOne(gpa)).* = @intCast(reloc_i); }, else => continue, @@ -5481,6 +5551,8 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { defer prev_loc_si = loc_si; const loc_sym = loc_si.get(coff); + + // TODO: Make this a helper for anything that needs to report "referenced by" notes switch (coff.getNode(loc_sym.ni)) { .data_directories => { const dir_align = std.mem.Alignment.of(std.coff.ImageDataDirectory); @@ -5958,7 +6030,7 @@ fn flushUav( else => |e| return e, }; si.get(coff).extra.size = @intCast(nw.interface.end); - si.applyLocationRelocs(coff); + try si.applyLocationRelocs(coff); } fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !void { @@ -5998,7 +6070,7 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v sym.gmi = alias_sym.gmi; coff.globals.values()[gmi.unwrap().?] = alias_si; // Only apply the new relocs - alias_si.applyTargetRelocs(coff, prev_target_relocs); + try alias_si.applyTargetRelocs(coff, prev_target_relocs); } fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { @@ -6407,7 +6479,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }, } - si.flushMoved(coff); + try si.flushMoved(coff); return true; } @@ -6575,7 +6647,7 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { else => |e| return e, }; si.get(coff).extra.size = @intCast(nw.interface.end); - si.applyLocationRelocs(coff); + try si.applyLocationRelocs(coff); } fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { @@ -6644,10 +6716,10 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { } }, .input_section => |isi| { - isi.symbol(coff).flushMoved(coff); + try isi.symbol(coff).flushMoved(coff); for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |input_symbol| { if (input_symbol.si.get(coff).ni != ni) break; - input_symbol.si.flushMoved(coff); + try input_symbol.si.flushMoved(coff); } }, .import_directory_table => coff.targetStore( @@ -6661,14 +6733,14 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .import_address_table => |import_index| { const entry = import_index.get(coff); const import_address_table_si = entry.import_address_table_si; - import_address_table_si.flushMoved(coff); + try 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); + try iat_ptr_si.flushMoved(coff); }, .import_hint_name_table => |import_index| { const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); @@ -6721,12 +6793,12 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable)); }, .export_address_table => { - coff.export_table.export_address_table_si.flushMoved(coff); + try coff.export_table.export_address_table_si.flushMoved(coff); // These relocs are applied directly here instead of via the above flushMoved call as // they are non-contiguous, and not tracked under export_address_table_si. for (coff.export_table.entries.values()) |entry| - entry.export_address_table_ri.get(coff).apply(coff); + try entry.export_address_table_ri.get(coff).apply(coff); coff.targetStore( &coff.exportDirectoryTable().export_address_table_rva, @@ -6762,7 +6834,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .uav, .lazy_code, .lazy_const_data, - => |mi| mi.symbol(coff).flushMoved(coff), + => |mi| try mi.symbol(coff).flushMoved(coff), } try ni.childrenMoved(coff.base.comp.gpa, &coff.mf); } @@ -7131,7 +7203,7 @@ fn updateExportsInner( export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; export_sym.section_number = exported_sym.section_number; - defer export_si.applyTargetRelocs(coff, .none); + defer export_si.applyTargetRelocs(coff, .none) catch unreachable; const prev_alias_sym = prev_alias_si.get(coff); switch (prev_alias_sym.flags.extra_tag) { diff --git a/test/link.zig b/test/link.zig index 1d0a799e4f46e4d3f00aa6639667d830b1454b69..eba9d1ca0b07e9e902c8bb9a3eeaeb9730937681 100644 --- a/test/link.zig +++ b/test/link.zig @@ -93,6 +93,65 @@ pub fn addCases(ctx: *LinkContext) void { const run = case.addRunArtifact(exe); run.addCheck(.{ .expect_term = .{ .exited = 0 } }); } + + if (ctx.includeTest("abs-symbol")) |case| { + const abs = case.addObject(.{ + .name = "abs", + .use_llvm = true, // TODO: .globl not supported on self-hosted + .use_lld = true, + .asm_source_bytes = + \\.globl foo + \\foo = 0xcafecafe + \\ + , + }); + + const abs_reloc = case.addObject(.{ + .name = "abs_reloc", + .use_llvm = true, // TODO: .globl not supported on self-hosted + .use_lld = true, + .asm_source_bytes = + \\.data + \\.globl foo_copy + \\foo_copy: + \\.long foo + , + }); + + case.verifyObjdump(abs_reloc, &.{ + "-s", + "--relocs", + }, .{ .arch = true }); + + const exe_reloc_err = case.addExecutable(.{ + .name = "test-reloc-err", + .zig_source_bytes = + \\extern const foo: usize; + \\pub fn main() !u8 { + \\ return @intFromBool(foo != 0xcafecafe); + \\} + , + }); + exe_reloc_err.root_module.addObject(abs); + case.expectLinkErrors(exe_reloc_err, .{ + .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/", + }); + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = + \\extern var foo_copy: u32; + \\pub fn main() !u8 { + \\ return @intFromBool(foo_copy != 0xcafecafe); + \\} + , + }); + exe.root_module.addObject(abs); + exe.root_module.addObject(abs_reloc); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 0 } }); + } } const LinkContext = @import("tests.zig").LinkContext; diff --git a/test/link/snapshots/abs-symbol-x86_64.dmp b/test/link/snapshots/abs-symbol-x86_64.dmp new file mode 100644 index 0000000000000000000000000000000000000000..747546649b082633b0097b1be311a382e0b98e2b --- /dev/null +++ b/test/link/snapshots/abs-symbol-x86_64.dmp @@ -0,0 +1 @@ +xxxxxxxx ADDR32 xxxxxxxx UNDEF | foo diff --git a/test/src/Link.zig b/test/src/Link.zig index 9982e50e12d3b77b485db3cc1066db3a27689001..6d064eaf27bc13e47a89fd454bbd818d0e373294 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -90,6 +90,16 @@ pub const Case = struct { }); } + pub fn expectLinkErrors( + self: *const Case, + comp: *Step.Compile, + expected_errors: Step.Compile.ExpectedCompileErrors, + ) void { + comp.expect_errors = expected_errors; + const bin_file = comp.getEmittedBin(); + bin_file.addStepDependencies(self.ctx.step); + } + const SnapshotScope = struct { /// If a test case has multiple verifyObjdump calls, `opt_sub_name` can /// be used to differentiate them. -- 2.54.0 From 7f0b5787fb1db871501bc4b03629d68dc378e038 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 69/94] objdump: various fixes - symbol filtering applies to import headers - fix missing formatting hooks - fix referencing stale memory for reloc symbol names (short names) - improved output when the user requests things that don't exist in the file --- lib/compiler/objdump.zig | 68 +++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index dc76d6da0d9c1f363bef8636d0b0a644eb75cb85..5b006fb72bd641ee6b2e304085cbc105e2079cc1 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -76,12 +76,14 @@ pub fn main(init: std.process.Init) !void { return Io.File.stdout().writeStreamingAll(io, usage); } else if (mem.eql(u8, arg, "--all-headers")) { opt_file_headers = true; + opt_linker_member = .second_linker; opt_member_headers = true; opt_section_headers = true; opt_symbols = true; opt_relocs = true; } else if (mem.eql(u8, arg, "--exports")) { opt_exports = true; + opt_linker_member = .second_linker; } else if (mem.eql(u8, arg, "--file-headers")) { opt_file_headers = true; } else if (mem.eql(u8, arg, "--imports")) { @@ -551,12 +553,21 @@ const coff = struct { const sig = std.mem.readInt(u16, member_sig[2..4], .little); const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; - if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) { + if (d.opts.member_headers) try dumpArchiveHeader(d, &header, member.offset); + + if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) { if (is_imp_lib) { - try w.writeAll("\nImport header:\n"); - const imp_header = try r.takeStruct(std.coff.ImportHeader, .little); + const sym_name = (try r.takeDelimiter(0)).?; + const imp_dll = (try r.takeDelimiter(0)).?; + + if (!filterMatches(d.opts.symbol_filters, sym_name)) + continue; + + if (d.element(.@"header-name")) + try w.writeAll("\nImport header:\n"); + try dumpHeader(d, std.coff.ImportHeader, &imp_header, struct { pub fn sig1(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {} pub fn sig2(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {} @@ -569,8 +580,6 @@ const coff = struct { } }); - const sym_name = (try r.takeDelimiter(0)).?; - const imp_dll = (try r.takeDelimiter(0)).?; const imp_name = imp_name: switch (imp_header.types.name_type) { .NAME_NOPREFIX, .NAME_UNDECORATE, @@ -847,7 +856,7 @@ const coff = struct { try dumpFlags(w, "{s}", std.coff.SectionHeader.Flags, §ion.header.flags, 1); if (section.name.len > 8) - try w.print("| {s}", .{section.name}); + try w.print("\n | {s}", .{section.name}); try w.writeByte('\n'); } @@ -861,6 +870,10 @@ const coff = struct { section_number: std.coff.SectionNumber, }) = .empty; defer symbols.deinit(gpa); + + var name_arena: std.heap.ArenaAllocator = .init(gpa); + defer name_arena.deinit(); + if (d.opts.relocs) try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); @@ -893,7 +906,7 @@ const coff = struct { &.{}; defer symbol_i += symbol.number_of_aux_symbols + 1; - const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { + const name = if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { const index = std.mem.readInt(u32, symbol.name[4..], .little); if (index >= string_table.len) return d.failParse("invalid name offset for symbol {x} ({x} >= {x})", .{ @@ -901,8 +914,8 @@ const coff = struct { index, string_table.len, }); - break :name string_table[index..]; - } else &symbol.name, 0); + break :name std.mem.sliceTo(string_table[index..], 0); + } else try name_arena.allocator().dupe(u8, std.mem.sliceTo(&symbol.name, 0)); if (d.opts.relocs) symbols.appendNTimesAssumeCapacity(.{ @@ -1152,8 +1165,17 @@ const coff = struct { } else &.{}; defer gpa.free(rva_index); - if (d.opts.exports) { - if (try seekToDataDirectory(d, rva_index, sections.items, image_info.?.data_dirs, .EXPORT)) |section_index| { + if (d.opts.exports) exports: { + if (try seekToDataDirectory( + d, + rva_index, + sections.items, + (image_info orelse { + try w.writeAll("COFF objects do not contain an export data directory"); + break :exports; + }).data_dirs, + .EXPORT, + )) |section_index| { const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err| return d.failParse("unable to read export directory: {t}", .{err}); @@ -1239,12 +1261,15 @@ const coff = struct { } } - if (d.opts.imports) { + if (d.opts.imports) imports: { if (try seekToDataDirectory( d, rva_index, sections.items, - image_info.?.data_dirs, + (image_info orelse { + try w.writeAll("COFF objects do not contain an import data directory"); + break :imports; + }).data_dirs, .IMPORT, )) |_| { const Entry = std.coff.ImportDirectoryEntry; @@ -1374,12 +1399,15 @@ const coff = struct { } } - if (d.opts.tls) { + if (d.opts.tls) tls: { if (try seekToDataDirectory( d, rva_index, sections.items, - image_info.?.data_dirs, + (image_info orelse { + try w.writeAll("COFF objects do not contain a TLS data directory"); + break :tls; + }).data_dirs, .TLS, )) |_| { switch (image_info.?.magic) { @@ -1579,7 +1607,8 @@ const coff = struct { } fn dumpArchiveHeader(d: *const DumpContext, header: *const ArchiveHeader, pos: u32) !void { - try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); + if (d.element(.@"header-name")) + try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); try dumpHeader(d, ArchiveHeader, header, struct { pub fn name(_: *const DumpContext, _: *const ArchiveHeader) !void {} pub fn file_mode(id: *const DumpContext, h: *const ArchiveHeader) !void { @@ -1595,7 +1624,8 @@ const coff = struct { std.mem.endsWith(u8, name, "_address") or std.mem.startsWith(u8, name, "pointer_")) return .va; - if (std.mem.startsWith(u8, name, "number_")) + if (std.mem.startsWith(u8, name, "number_") or + std.mem.startsWith(u8, name, "size")) return .size; return null; } @@ -1658,8 +1688,8 @@ const usage = \\ \\Options: \\ -h, --help Print this help and exit - \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols - \\ --exports Display exported symbols + \\ --all-headers Alias for --file-headers --linker-member=2 --member-headers --section-headers --relocs --symbols + \\ --exports Display exported symbols. In the case of COFF import libraries, display import headers. \\ --file-headers Display file-format specific headers \\ --imports Display imported symbols \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2) -- 2.54.0 From 72bc140ad16a4a2dbe632129ea9068630e019277 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 70/94] test/link: separate out shared lib tests - Fixup snapshot naming - Fixup verifyObjdumps so we can dump other artifacts - Add llvm link_targets for comparison --- lib/compiler/objdump.zig | 13 +- test/link.zig | 132 +++++++++++++----- test/link/snapshots/abs-symbol-x86_64.dmp | 1 - .../dynamic-lib-code.implib-windows.dmp | 32 +++++ ...namic.dmp => dynamic-lib-code.windows.dmp} | 5 +- test/link/snapshots/emit-static-lib.dmp | 10 -- test/link/snapshots/exports-static.dmp | 3 - test/src/Link.zig | 47 +++++-- test/tests.zig | 38 +++++ 9 files changed, 207 insertions(+), 74 deletions(-) delete mode 100644 test/link/snapshots/abs-symbol-x86_64.dmp create mode 100644 test/link/snapshots/dynamic-lib-code.implib-windows.dmp rename test/link/snapshots/{exports-dynamic.dmp => dynamic-lib-code.windows.dmp} (79%) delete mode 100644 test/link/snapshots/emit-static-lib.dmp delete mode 100644 test/link/snapshots/exports-static.dmp diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 5b006fb72bd641ee6b2e304085cbc105e2079cc1..ae0a375e8081345697028572dc6cd331e13a4a1a 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -401,12 +401,13 @@ const coff = struct { , .{ expected_kind, num_symbols }); if (d.opts.linker_member == .first_linker) { - try w.writeAll( - \\ - \\Archive symbols: - \\& Member Symbol - \\ - ); + if (d.element(.@"table-header")) + try w.writeAll( + \\ + \\Archive symbols: + \\& Member Symbol + \\ + ); const offsets = try r.readAlloc(gpa, num_symbols * 4); defer gpa.free(offsets); diff --git a/test/link.zig b/test/link.zig index eba9d1ca0b07e9e902c8bb9a3eeaeb9730937681..5b76036e7dc84c0a15d17bd472e0c1d263391439 100644 --- a/test/link.zig +++ b/test/link.zig @@ -1,29 +1,5 @@ pub fn addCases(ctx: *LinkContext) void { - if (ctx.includeTest("exports-static")) |case| { - const lib = case.addLibrary(.static, .{ - .name = "lib", - .zig_source_file = ctx.sourcePath("exports.zig"), - }); - case.verifyObjdump(lib, &.{ - "-s", - "--symbols", - "--only-symbol=foo", - }, .{}); - } - - if (ctx.includeTest("exports-dynamic")) |case| { - const lib = case.addLibrary(.dynamic, .{ - .name = "lib", - .zig_source_file = ctx.sourcePath("exports.zig"), - }); - case.verifyObjdump(lib, &.{ - "-s", - "--exports", - "--only-symbol=foo", - }, .{}); - } - - if (ctx.includeTest("emit-static-lib")) |case| { + if (ctx.includeTest("static-lib")) |case| { const obj1 = case.addObject(.{ .name = "obj1", .name_prefix = false, @@ -43,14 +19,14 @@ pub fn addCases(ctx: *LinkContext) void { .name_prefix = false, .name_target = false, .zig_source_bytes = - \\fn weakFoo() callconv(.c) usize { + \\fn fooWeak() callconv(.c) usize { \\ return 0xaabbccddaabbccdd; \\} - \\export var array_foo: [2]u16 = .{ 0xffff, 0xabcd }; - \\export var strong_foo: usize = 0x1122334411223344; + \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd }; + \\export var foo_strong: usize = 0x1122334411223344; \\comptime { - \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak }); - \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong }); + \\ @export(&fooWeak, .{ .name = "fooWeak", .linkage = .weak }); + \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong }); \\} , }); @@ -63,25 +39,109 @@ pub fn addCases(ctx: *LinkContext) void { lib.root_module.addObject(obj1); lib.root_module.addObject(obj2); - case.verifyObjdump(lib, &.{ + case.verifyObjdump(lib.getEmittedBin(), &.{ "-s", "--elements=file-type", "--symbols", "--only-symbol=foo", - "--only-symbol=Foo", }, .{}); const exe = case.addExecutable(.{ .name = "test", .zig_source_bytes = \\extern fn fooBar() c_uint; - \\extern fn weakFoo() usize; + \\extern fn fooWeak() usize; + \\extern var foo_array: [2]u16; + \\extern var foo_strong: usize; + \\extern var foo_strong_alias: usize; + \\pub fn main() !u8 { + \\ return @intFromBool(0xcd003365cd00df35 != fooBar() + + \\ fooWeak() + + \\ foo_array[1] + + \\ foo_strong + + \\ foo_strong_alias); + \\} + , + }); + exe.root_module.linkLibrary(lib); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 0 } }); + } + + if (ctx.includeTest("dynamic-lib-code")) |case| { + const lib = case.addLibrary(.dynamic, .{ + .name = "lib", + .zig_source_bytes = + \\export fn foo1() callconv(.c) u64 { + \\ return 0x1122334411223344; + \\} + \\export fn foo2() callconv(.c) u64 { + \\ return 0xaabbccddaabbccdd; + \\} + , + }); + + case.verifyObjdump(lib.getEmittedBin(), &.{ + "-s", + "--exports", + "--only-symbol=foo", + }, .{ .os = true }); + + if (ctx.target.result.os.tag == .windows) { + case.verifyObjdump(lib.getEmittedImplib(), &.{ + "-s", + "--exports", + "--only-symbol=foo", + }, .{ .sub_name = "implib", .os = true }); + } + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = + \\extern fn foo1() u64; + \\pub fn main() !u8 { + \\ const foo2 = @extern( + \\ *const fn () callconv(.c) u64, + \\ .{ .name = "foo2", .is_dll_import = true }, + \\ ); + \\ return @intFromBool(0xbbde0021bbde0021 != foo1() + foo2()); + \\} + , + }); + exe.root_module.linkLibrary(lib); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 0 } }); + } + + if (ctx.includeTest("dynamic-lib-data")) |case| { + const lib = case.addLibrary(.dynamic, .{ + .name = "lib", + .zig_source_bytes = + \\export var array_foo: [2]u16 = .{ 0xffff, 0xabcd }; + \\export var strong_foo: usize = 0x1122334411223344; + , + }); + + case.verifyObjdump(lib.getEmittedBin(), &.{ + "-s", + "--exports", + "--only-symbol=foo", + }, .{}); + + if (ctx.target.result.os.tag == .windows) { + // TODO: objdump implib on windows + } + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = \\extern var array_foo: [2]u16; \\extern var strong_foo: usize; \\extern var strong_foo_alias: usize; \\pub fn main() !u8 { - \\ return @intFromBool(0xcd003365cd00df35 != fooBar() + - \\ weakFoo() + + \\ return @intFromBool(0x2244668822451255 != \\ array_foo[1] + \\ strong_foo + \\ strong_foo_alias); @@ -118,7 +178,7 @@ pub fn addCases(ctx: *LinkContext) void { , }); - case.verifyObjdump(abs_reloc, &.{ + case.verifyObjdump(abs_reloc.getEmittedBin(), &.{ "-s", "--relocs", }, .{ .arch = true }); diff --git a/test/link/snapshots/abs-symbol-x86_64.dmp b/test/link/snapshots/abs-symbol-x86_64.dmp deleted file mode 100644 index 747546649b082633b0097b1be311a382e0b98e2b..0000000000000000000000000000000000000000 --- a/test/link/snapshots/abs-symbol-x86_64.dmp +++ /dev/null @@ -1 +0,0 @@ -xxxxxxxx ADDR32 xxxxxxxx UNDEF | foo diff --git a/test/link/snapshots/dynamic-lib-code.implib-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-windows.dmp new file mode 100644 index 0000000000000000000000000000000000000000..fdc53a6e7fc007ff24a7459b9914b910ac1bf467 --- /dev/null +++ b/test/link/snapshots/dynamic-lib-code.implib-windows.dmp @@ -0,0 +1,32 @@ + 0 date + 0 user_id + 0 group_id + 0 file_mode +xxxxxxxxxxxxxxxx size + second_linker type + | 7 symbols + | 5 members +xxxxxxxx __imp_foo1 +xxxxxxxx __imp_foo2 +xxxxxxxx foo1 +xxxxxxxx foo2 + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data + 0 hint + CODE import_type + NAME name_type + symbol name | foo1 + import name | foo1 + dll | dynamic-lib-code-lib-x86_64-windows.win10...win11_dt-msvc-Debug-llvm-lld-libc.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data + 0 hint + CODE import_type + NAME name_type + symbol name | foo2 + import name | foo2 + dll | dynamic-lib-code-lib-x86_64-windows.win10...win11_dt-msvc-Debug-llvm-lld-libc.dll diff --git a/test/link/snapshots/exports-dynamic.dmp b/test/link/snapshots/dynamic-lib-code.windows.dmp similarity index 79% rename from test/link/snapshots/exports-dynamic.dmp rename to test/link/snapshots/dynamic-lib-code.windows.dmp index fbd6415e5f9564d2299ab13ca81b75dde3100001..a9396675f8f34175fc4b3a5f488a003cc60bc63d 100644 --- a/test/link/snapshots/exports-dynamic.dmp +++ b/test/link/snapshots/dynamic-lib-code.windows.dmp @@ -9,6 +9,5 @@ xxxxxxxxxxxxxxxx number_of_names xxxxxxxxxxxxxxxx export_address_table_rva xxxxxxxxxxxxxxxx name_pointer_table_rva xxxxxxxxxxxxxxxx ordinal_table_rva -xxxx xxxx xxxxxxxx | foo_const -xxxx xxxx xxxxxxxx | foo_fn -xxxx xxxx xxxxxxxx | foo_var +xxxx xxxx xxxxxxxx | foo1 +xxxx xxxx xxxxxxxx | foo2 diff --git a/test/link/snapshots/emit-static-lib.dmp b/test/link/snapshots/emit-static-lib.dmp deleted file mode 100644 index 41932df9785ea10382d73473f93e8e4b16a3fe72..0000000000000000000000000000000000000000 --- a/test/link/snapshots/emit-static-lib.dmp +++ /dev/null @@ -1,10 +0,0 @@ -lib.lib: COFF archive -lib.lib(obj1.obj): COFF object -xxxx 00000000 1 NULL() EXTERNAL | fooBar -xxxx 00000000 2 NULL EXTERNAL | foo1 -xxxx 00000004 2 NULL EXTERNAL | foo2 -lib.lib(this_is_a_long_name.obj): COFF object -xxxx 00000000 4 NULL() EXTERNAL | weakFoo -xxxx 00000010 2 NULL EXTERNAL | array_foo -xxxx 00000000 2 NULL EXTERNAL | strong_foo_alias -xxxx 00000000 2 NULL EXTERNAL | strong_foo diff --git a/test/link/snapshots/exports-static.dmp b/test/link/snapshots/exports-static.dmp deleted file mode 100644 index d112fcbe7191bc481d50ad89592d86e2d0b4b576..0000000000000000000000000000000000000000 --- a/test/link/snapshots/exports-static.dmp +++ /dev/null @@ -1,3 +0,0 @@ -xxxx 00000000 4 NULL() EXTERNAL | foo_fn -xxxx 00000000 2 NULL EXTERNAL | foo_var -xxxx 00000008 3 NULL EXTERNAL | foo_const diff --git a/test/src/Link.zig b/test/src/Link.zig index 6d064eaf27bc13e47a89fd454bbd818d0e373294..4b621d5be89d0cebaddee9c20fefef8578bfea9d 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -101,7 +101,7 @@ pub const Case = struct { } const SnapshotScope = struct { - /// If a test case has multiple verifyObjdump calls, `opt_sub_name` can + /// If a test case has multiple verifyObjdump calls, `opt_sub_name` should /// be used to differentiate them. sub_name: ?[]const u8 = null, arch: bool = false, @@ -120,7 +120,7 @@ pub const Case = struct { /// pub fn verifyObjdump( self: *const Case, - compile: *Step.Compile, + file: Build.LazyPath, args: []const []const u8, scope: SnapshotScope, ) void { @@ -140,7 +140,7 @@ pub const Case = struct { .{ snapshot_name, ctx.target_desc }, )); run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" }); - run_step.addArtifactArg(compile); + run_step.addFileArg(file); run_step.addArgs(args); run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); @@ -166,22 +166,39 @@ pub const Case = struct { const w = &snapshot_name.writer; try w.writeAll(self.prefix); - if (scope.sub_name) |sub_name| { - try w.writeByte('.'); - try w.writeAll(sub_name); - } + var sep: u8 = '.'; - if (scope.arch) try w.print("-{t}", .{ctx.target.result.cpu.arch}); - if (scope.os) try w.print("-{t}", .{ctx.target.result.os.tag}); - if (scope.abi) try w.print("-{t}", .{ctx.target.result.abi}); - if (scope.optimize) try w.print("-{t}", .{ctx.optimize}); - if (scope.use_llvm) try w.writeAll(if (ctx.use_llvm) "-llvm" else "-no-llvm"); - if (scope.use_lld) try w.writeAll(if (ctx.use_lld) "-lld" else "-no-lld"); - if (scope.link_libc) try w.writeAll(if (ctx.link_libc) "-libc" else "-no-libc"); - try w.writeAll(".dmp"); + if (try snapshotNameInner(w, scope.sub_name != null, &sep)) + try w.writeAll(scope.sub_name.?); + if (try snapshotNameInner(w, scope.arch, &sep)) + try w.print("{t}", .{ctx.target.result.cpu.arch}); + if (try snapshotNameInner(w, scope.os, &sep)) + try w.print("{t}", .{ctx.target.result.os.tag}); + if (try snapshotNameInner(w, scope.abi, &sep)) + try w.print("{t}", .{ctx.target.result.abi}); + if (try snapshotNameInner(w, scope.optimize, &sep)) + try w.print("{t}", .{ctx.optimize}); + if (try snapshotNameInner(w, scope.use_llvm, &sep)) + try w.writeAll(if (ctx.use_llvm) "llvm" else "no-llvm"); + if (try snapshotNameInner(w, scope.use_lld, &sep)) + try w.writeAll(if (ctx.use_lld) "lld" else "no-lld"); + if (try snapshotNameInner(w, scope.link_libc, &sep)) + try w.writeAll(if (ctx.link_libc) "libc" else "no-libc"); + + if (sep == '-') try w.writeByte('.'); + try w.writeAll("dmp"); return try snapshot_name.toOwnedSlice(); } + + fn snapshotNameInner(w: *std.Io.Writer, cond: bool, sep: *u8) !bool { + if (cond) { + try w.writeByte(sep.*); + sep.* = '-'; + } + + return cond; + } }; fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { diff --git a/test/tests.zig b/test/tests.zig index 80366c7f5c7937f3c07b40ac90611c72ec2bf890..2159d8a8a05ea45b900ed4beec64141ddb9624cf 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2094,12 +2094,48 @@ const link_targets = blk: { }, .link_libc = true, }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + .use_llvm = true, + .use_lld = true, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + .link_libc = true, + .use_llvm = true, + .use_lld = true, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .msvc, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .msvc, + }, + .link_libc = true, + }, .{ .target = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .msvc, }, + .use_llvm = true, + .use_lld = true, }, .{ .target = .{ @@ -2108,6 +2144,8 @@ const link_targets = blk: { .abi = .msvc, }, .link_libc = true, + .use_llvm = true, + .use_lld = true, }, }; }; -- 2.54.0 From 1b95427715b58cf4cd6df63fc9a7a8d200ca5d47 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 71/94] Coff: add support for .is_dll_import x86_64: emit the correct encodings for loading / calling .is_dll_import externs Coff: fix emitting an empty import directory objdump: add --exports=sort for sorting implibs in snapshots (self-hosted outputs members in a different order than llvm) objdump: fix some missed redactions / filters / elements calls test: add dllimport standalone test --- lib/compiler/objdump.zig | 102 ++++++++++++++---- src/codegen/x86_64/Emit.zig | 30 +++++- src/link/Coff.zig | 27 +++-- test/link.zig | 33 +++--- test/link/snapshots/abs-symbol.x86_64.dmp | 1 + .../dynamic-lib-code.implib-windows.dmp | 4 +- ...dynamic-lib-code.implib-x86_64-windows.dmp | 32 ++++++ test/link/snapshots/dynamic-lib-data.dmp | 14 +++ ...dynamic-lib-data.implib-x86_64-windows.dmp | 41 +++++++ test/src/Link.zig | 3 +- test/standalone/shared_library/mathtest.zig | 2 + test/standalone/shared_library/test.c | 9 ++ 12 files changed, 250 insertions(+), 48 deletions(-) create mode 100644 test/link/snapshots/abs-symbol.x86_64.dmp create mode 100644 test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp create mode 100644 test/link/snapshots/dynamic-lib-data.dmp create mode 100644 test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index ae0a375e8081345697028572dc6cd331e13a4a1a..0ffb3295fa5834a985d1c91f693077b14bd8dc15 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -11,6 +11,7 @@ var stdout_buffer: [4000]u8 = undefined; const Options = struct { exports: bool, + exports_sort: bool, file_headers: bool, imports: bool, input_path: []const u8, @@ -53,6 +54,7 @@ pub fn main(init: std.process.Init) !void { var i: usize = 1; var opt_exports: ?bool = null; + var opt_exports_sort: ?bool = null; var opt_file_headers: ?bool = null; var opt_imports: ?bool = null; var opt_input_path: ?[]const u8 = null; @@ -81,9 +83,11 @@ pub fn main(init: std.process.Init) !void { opt_section_headers = true; opt_symbols = true; opt_relocs = true; - } else if (mem.eql(u8, arg, "--exports")) { + } else if (mem.startsWith(u8, arg, "--exports")) { opt_exports = true; opt_linker_member = .second_linker; + if (mem.eql(u8, arg["--exports".len..], "=sort")) + opt_exports_sort = true; } else if (mem.eql(u8, arg, "--file-headers")) { opt_file_headers = true; } else if (mem.eql(u8, arg, "--imports")) { @@ -156,6 +160,7 @@ pub fn main(init: std.process.Init) !void { const opts: Options = .{ .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), .exports = opt_exports orelse false, + .exports_sort = opt_exports_sort orelse false, .file_headers = opt_file_headers orelse false, .imports = opt_imports orelse false, .linker_member = opt_linker_member, @@ -355,9 +360,12 @@ const coff = struct { const r = &fr.interface; r.toss(std.coff.archive_signature.len); - var members: std.ArrayList(struct { + const Member = struct { offset: u32, - }) = .empty; + order: ?u32, + }; + + var members: std.ArrayList(Member) = .empty; defer members.deinit(gpa); var symbol_member_indices: std.ArrayList(u32) = .empty; defer symbol_member_indices.deinit(gpa); @@ -415,6 +423,10 @@ const coff = struct { for (0..num_symbols) |symbol_i| { const symbol = r.takeDelimiter(0) catch |err| return d.failParse("unable to read first linker member string table: {t}", .{err}); + + if (!filterMatches(d.opts.symbol_filters, symbol.?)) + continue; + const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big); try w.print("{f} {s}\n", .{ fmtIntField(d, offset, .{ .kind = .va }), @@ -441,6 +453,7 @@ const coff = struct { for (0..num_members) |_| members.addOneAssumeCapacity().* = .{ .offset = try r.takeInt(u32, .little), + .order = null, }; const num_symbols = try r.takeInt(u32, .little); @@ -451,14 +464,42 @@ const coff = struct { if (dump_header) try w.print( \\{t: >16} type - \\ | {d} symbols - \\ | {d} members + \\ | {f} symbols + \\ | {f} members \\ - , .{ expected_kind, num_symbols, num_members }); + , .{ + expected_kind, + fmtIntField(d, num_symbols, .{ .kind = .size, .width = .auto }), + fmtIntField(d, num_members, .{ .kind = .size, .width = .auto }), + }); try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); - for (0..num_symbols) |_| - symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1; + for (0..num_symbols) |order| { + const index = (try r.takeInt(u16, .little)) - 1; + if (index >= members.items.len) + return d.failParse("invalid member index 0x{x} in seconds linker member indices array", .{index}); + + symbol_member_indices.addOneAssumeCapacity().* = index; + + if (members.items[index].order == null) + members.items[index].order = @intCast(order); + } + + if (d.opts.exports and d.opts.exports_sort) { + std.sort.pdq(Member, members.items, {}, struct { + fn lessThan(ctx: void, lhs: Member, rhs: Member) bool { + _ = ctx; + if (lhs.order == null and rhs.order == null) + return lhs.offset < rhs.offset + else if (lhs.order) |lhs_order| + return if (rhs.order) |rhs_order| lhs_order < rhs_order else false + else if (rhs.order) |rhs_order| + return if (lhs.order) |lhs_order| lhs_order < rhs_order else true + else + unreachable; + } + }.lessThan); + } if (d.opts.linker_member == .second_linker) { if (d.element(.@"table-header")) @@ -480,6 +521,9 @@ const coff = struct { else => |e| return e, }) |n| n else return d.failParse("unterminated string found in second linker member", .{}); + if (!filterMatches(d.opts.symbol_filters, symbol_name)) + continue; + try w.print("{f} {s}\n", .{ fmtIntField( d, @@ -844,8 +888,8 @@ const coff = struct { section_i + 1, raw_name, fmtIntField(d, section.header.virtual_address, .{ .kind = .va }), - fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = 9 }), - fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = 9 }), + fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = .{ .explicit = 9 } }), + fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = .{ .explicit = 9 } }), fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }), fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }), fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }), @@ -1309,14 +1353,16 @@ const coff = struct { const dll_name = (try r.takeDelimiter(0)).?; - try w.print("Import table entry for {s}:\n", .{dll_name}); + if (d.element(.@"header-name")) + try w.print("Import table entry for {s}:\n", .{dll_name}); try dumpHeader(d, Entry, &entry, struct {}); - try w.print( - \\ - \\ Ord Hint Name - \\ - , .{}); + if (d.element(.@"table-header")) + try w.print( + \\ + \\ Ord Hint Name + \\ + , .{}); const ilt_section = sectionContainingRva( rva_index, @@ -1565,7 +1611,7 @@ const coff = struct { const FormatIntField = struct { val: ?u64, - width: usize, + width: ?usize, zero_fill: bool, }; @@ -1574,14 +1620,22 @@ const coff = struct { val: anytype, params: struct { kind: ?FieldKind = null, - width: ?usize = null, + width: union(enum) { + fit_max, + auto, + explicit: usize, + } = .fit_max, zero_fill: bool = false, }, ) std.fmt.Alt(FormatIntField, intFieldString) { return .{ .data = .{ .val = if (d.redacted(params.kind)) null else val, - .width = params.width orelse @typeInfo(@TypeOf(val)).int.bits / 4, + .width = switch (params.width) { + .fit_max => @typeInfo(@TypeOf(val)).int.bits / 4, + .auto => null, + .explicit => |w| w, + }, .zero_fill = params.zero_fill, }, }; @@ -1594,7 +1648,7 @@ const coff = struct { .alignment = .right, .fill = if (field.zero_fill) '0' else ' ', }); - } else try w.splatByteAll('x', field.width); + } else try w.splatByteAll('x', field.width orelse 1); } fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void { @@ -1628,6 +1682,8 @@ const coff = struct { if (std.mem.startsWith(u8, name, "number_") or std.mem.startsWith(u8, name, "size")) return .size; + if (std.mem.startsWith(u8, name, "hint")) + return .ord; return null; } @@ -1645,7 +1701,7 @@ const coff = struct { switch (@typeInfo(field.type)) { .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{ .kind = comptime fieldKind(field.name), - .width = 16, + .width = .{ .explicit = 16 }, }), field.name }), .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }), .@"struct" => |s| { @@ -1690,7 +1746,9 @@ const usage = \\Options: \\ -h, --help Print this help and exit \\ --all-headers Alias for --file-headers --linker-member=2 --member-headers --section-headers --relocs --symbols - \\ --exports Display exported symbols. In the case of COFF import libraries, display import headers. + \\ --exports[=sort] Display exported symbols. + \\ In the case of COFF import libraries, displays the symbol list and import headers. + \\ Specify =sort to optionally sort the import headers by symbol name. \\ --file-headers Display file-format specific headers \\ --imports Display imported symbols \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2) diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index 304bde522aa2328d0f8ee7e8f4728767c14e08c7..d4677bfe3259fa719e54c4187ef0fb70ae98d79a 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -113,6 +113,7 @@ pub fn emitMir(emit: *Emit) Error!void { .default => true, .hidden, .protected => false, }, + .is_dll_import = @"extern".is_dll_import, .force_pcrel_direct = switch (@"extern".relocation) { .any => false, .pcrel => true, @@ -171,7 +172,13 @@ pub fn emitMir(emit: *Emit) Error!void { switch (lowered_inst.encoding.mnemonic) { .call => { reloc.target = .{ .branch = target }; - try emit.encodeInst(lowered_inst, reloc_info); + if (target.is_dll_import and emit.bin_file.cast(.coff2) != null) { + try emit.encodeInst(try .new(.none, .call, &.{ + .{ .mem = .initRip(.ptr, 0) }, + }, emit.lower.target), reloc_info); + } else { + try emit.encodeInst(lowered_inst, reloc_info); + } continue :lowered_inst; }, else => {}, @@ -247,7 +254,25 @@ pub fn emitMir(emit: *Emit) Error!void { else => unreachable, } } else if (emit.bin_file.cast(.coff2)) |_| { - switch (lowered_inst.encoding.mnemonic) { + if (reloc.target.is_dll_import) switch (lowered_inst.encoding.mnemonic) { + .lea => try emit.encodeInst(try .new(.none, .mov, &.{ + lowered_inst.ops[0], + .{ .mem = .initRip(.ptr, 0) }, + }, emit.lower.target), reloc_info), + .mov => { + try emit.encodeInst(try .new(.none, .mov, &.{ + lowered_inst.ops[0], + .{ .mem = .initRip(.ptr, 0) }, + }, emit.lower.target), reloc_info); + try emit.encodeInst(try .new(.none, .mov, &.{ + lowered_inst.ops[0], + .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{ + .reg = lowered_inst.ops[0].reg.to64(), + } }) }, + }, emit.lower.target), &.{}); + }, + else => unreachable, + } else switch (lowered_inst.encoding.mnemonic) { .lea => try emit.encodeInst(try .new(.none, .lea, &.{ lowered_inst.ops[0], .{ .mem = .initRip(.none, 0) }, @@ -717,6 +742,7 @@ const RelocInfo = struct { const Symbol = struct { symbol: link.File.SymbolId, is_extern: bool, + is_dll_import: bool = false, force_pcrel_direct: bool = false, }; }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 6249cc30890dcc6e4efe8468421df562964e4fdf..951b3b27cedf408471218c5c341978b04ec7c833 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -2103,7 +2103,7 @@ fn initHeaders( coff.mf.flags.block_size, .{ .read = true, .initialized = true }, )).symbol(coff).node(coff), - .{ .alignment = .@"4", .moved = true }, + .{ .alignment = .@"4" }, ); coff.nodes.appendAssumeCapacity(.import_directory_table); @@ -6115,6 +6115,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const imp_match = std.mem.startsWith(u8, global_name, imp_prefix); // Globals may have the __imp_ prefix already if they are undef externals from another input. + assert(sym.flags.dll_storage_class != .dllexport); const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport) .{ gn.name, imp_match } else name: { @@ -6722,10 +6723,14 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { try input_symbol.si.flushMoved(coff); } }, - .import_directory_table => coff.targetStore( - &coff.dataDirectoryPtr(.IMPORT).virtual_address, - coff.computeNodeRva(ni), - ), + .import_directory_table => { + _, const size = ni.location(&coff.mf).resolve(&coff.mf); + if (size > 0) + coff.targetStore( + &coff.dataDirectoryPtr(.IMPORT).virtual_address, + coff.computeNodeRva(ni), + ); + }, .import_lookup_table => |import_index| coff.targetStore( &coff.importDirectoryEntryPtr(import_index).import_lookup_table_rva, coff.computeNodeRva(ni), @@ -6936,10 +6941,14 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { } }, .input_section => {}, - .import_directory_table => coff.targetStore( - &coff.dataDirectoryPtr(.IMPORT).size, - @intCast(size), - ), + .import_directory_table => { + const prev_size = coff.targetLoad(&coff.dataDirectoryPtr(.IMPORT).size); + coff.targetStore( + &coff.dataDirectoryPtr(.IMPORT).size, + @intCast(size), + ); + if (prev_size == 0) try coff.flushMoved(ni); + }, .import_lookup_table, .import_address_table, .import_hint_name_table, diff --git a/test/link.zig b/test/link.zig index 5b76036e7dc84c0a15d17bd472e0c1d263391439..baca548bbc44f3b09fbb435b67346411c001029d 100644 --- a/test/link.zig +++ b/test/link.zig @@ -72,6 +72,7 @@ pub fn addCases(ctx: *LinkContext) void { if (ctx.includeTest("dynamic-lib-code")) |case| { const lib = case.addLibrary(.dynamic, .{ .name = "lib", + .name_target = false, .zig_source_bytes = \\export fn foo1() callconv(.c) u64 { \\ return 0x1122334411223344; @@ -91,9 +92,9 @@ pub fn addCases(ctx: *LinkContext) void { if (ctx.target.result.os.tag == .windows) { case.verifyObjdump(lib.getEmittedImplib(), &.{ "-s", - "--exports", + "--exports=sort", "--only-symbol=foo", - }, .{ .sub_name = "implib", .os = true }); + }, .{ .sub_name = "implib", .os = true, .arch = true }); } const exe = case.addExecutable(.{ @@ -118,9 +119,13 @@ pub fn addCases(ctx: *LinkContext) void { if (ctx.includeTest("dynamic-lib-data")) |case| { const lib = case.addLibrary(.dynamic, .{ .name = "lib", + .name_target = false, .zig_source_bytes = - \\export var array_foo: [2]u16 = .{ 0xffff, 0xabcd }; - \\export var strong_foo: usize = 0x1122334411223344; + \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd }; + \\export var foo_strong: usize = 0x1122334411223344; + \\comptime { + \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong }); + \\} , }); @@ -131,20 +136,24 @@ pub fn addCases(ctx: *LinkContext) void { }, .{}); if (ctx.target.result.os.tag == .windows) { - // TODO: objdump implib on windows + case.verifyObjdump(lib.getEmittedImplib(), &.{ + "-s", + "--exports=sort", + "--only-symbol=foo", + }, .{ .sub_name = "implib", .os = true, .arch = true }); } const exe = case.addExecutable(.{ .name = "test", .zig_source_bytes = - \\extern var array_foo: [2]u16; - \\extern var strong_foo: usize; - \\extern var strong_foo_alias: usize; \\pub fn main() !u8 { - \\ return @intFromBool(0x2244668822451255 != - \\ array_foo[1] + - \\ strong_foo + - \\ strong_foo_alias); + \\ const foo_array = @extern(*[2]u16, .{ .name = "foo_array", .is_dll_import = true }); + \\ const foo_strong = @extern(*usize, .{ .name = "foo_strong", .is_dll_import = true }); + \\ const foo_strong_alias = @extern(*usize, .{ .name = "foo_strong_alias", .is_dll_import = true }); + \\ return @intFromBool(0x2244668822451255 != + \\ foo_array[1] + + \\ foo_strong.* + + \\ foo_strong_alias.*); \\} , }); diff --git a/test/link/snapshots/abs-symbol.x86_64.dmp b/test/link/snapshots/abs-symbol.x86_64.dmp new file mode 100644 index 0000000000000000000000000000000000000000..747546649b082633b0097b1be311a382e0b98e2b --- /dev/null +++ b/test/link/snapshots/abs-symbol.x86_64.dmp @@ -0,0 +1 @@ +xxxxxxxx ADDR32 xxxxxxxx UNDEF | foo diff --git a/test/link/snapshots/dynamic-lib-code.implib-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-windows.dmp index fdc53a6e7fc007ff24a7459b9914b910ac1bf467..850d38a618a3c2dad2c7f15aec0de3b9ace52808 100644 --- a/test/link/snapshots/dynamic-lib-code.implib-windows.dmp +++ b/test/link/snapshots/dynamic-lib-code.implib-windows.dmp @@ -19,7 +19,7 @@ xxxxxxxxxxxxxxxx size_of_data NAME name_type symbol name | foo1 import name | foo1 - dll | dynamic-lib-code-lib-x86_64-windows.win10...win11_dt-msvc-Debug-llvm-lld-libc.dll + dll | dynamic-lib-code-lib.dll 0 version 8664 machine (AMD64) 0 time_date_stamp @@ -29,4 +29,4 @@ xxxxxxxxxxxxxxxx size_of_data NAME name_type symbol name | foo2 import name | foo2 - dll | dynamic-lib-code-lib-x86_64-windows.win10...win11_dt-msvc-Debug-llvm-lld-libc.dll + dll | dynamic-lib-code-lib.dll diff --git a/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp new file mode 100644 index 0000000000000000000000000000000000000000..eed30ce985f4c8ab00f7534716ab5bed343edd8d --- /dev/null +++ b/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp @@ -0,0 +1,32 @@ + 0 date + 0 user_id + 0 group_id + 0 file_mode +xxxxxxxxxxxxxxxx size + second_linker type + | x symbols + | x members +xxxxxxxx __imp_foo1 +xxxxxxxx __imp_foo2 +xxxxxxxx foo1 +xxxxxxxx foo2 + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + CODE import_type + NAME name_type + symbol name | foo1 + import name | foo1 + dll | dynamic-lib-code-lib.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + CODE import_type + NAME name_type + symbol name | foo2 + import name | foo2 + dll | dynamic-lib-code-lib.dll diff --git a/test/link/snapshots/dynamic-lib-data.dmp b/test/link/snapshots/dynamic-lib-data.dmp new file mode 100644 index 0000000000000000000000000000000000000000..480b7107582590491d4e1094ff69f39ae9d44a61 --- /dev/null +++ b/test/link/snapshots/dynamic-lib-data.dmp @@ -0,0 +1,14 @@ +Export directory: + 0 flags + 0 time_date_stamp + 0.00 version +xxxxxxxxxxxxxxxx name_rva + 1 ordinal_base +xxxxxxxxxxxxxxxx number_of_entries +xxxxxxxxxxxxxxxx number_of_names +xxxxxxxxxxxxxxxx export_address_table_rva +xxxxxxxxxxxxxxxx name_pointer_table_rva +xxxxxxxxxxxxxxxx ordinal_table_rva +xxxx xxxx xxxxxxxx | foo_array +xxxx xxxx xxxxxxxx | foo_strong +xxxx xxxx xxxxxxxx | foo_strong_alias diff --git a/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp b/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp new file mode 100644 index 0000000000000000000000000000000000000000..b273ef44e475ff353e37271a9ec3777332082d5a --- /dev/null +++ b/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp @@ -0,0 +1,41 @@ + 0 date + 0 user_id + 0 group_id + 0 file_mode +xxxxxxxxxxxxxxxx size + second_linker type + | x symbols + | x members +xxxxxxxx __imp_foo_array +xxxxxxxx __imp_foo_strong +xxxxxxxx __imp_foo_strong_alias + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + DATA import_type + NAME name_type + symbol name | foo_array + import name | foo_array + dll | dynamic-lib-data-lib.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + DATA import_type + NAME name_type + symbol name | foo_strong + import name | foo_strong + dll | dynamic-lib-data-lib.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + DATA import_type + NAME name_type + symbol name | foo_strong_alias + import name | foo_strong_alias + dll | dynamic-lib-data-lib.dll diff --git a/test/src/Link.zig b/test/src/Link.zig index 4b621d5be89d0cebaddee9c20fefef8578bfea9d..dcb1f0eee613eb0b9d882ccca9ae7ba0051874f3 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -185,7 +185,8 @@ pub const Case = struct { if (try snapshotNameInner(w, scope.link_libc, &sep)) try w.writeAll(if (ctx.link_libc) "libc" else "no-libc"); - if (sep == '-') try w.writeByte('.'); + if (sep == '-') sep = '.'; + try w.writeByte(sep); try w.writeAll("dmp"); return try snapshot_name.toOwnedSlice(); diff --git a/test/standalone/shared_library/mathtest.zig b/test/standalone/shared_library/mathtest.zig index a04ec1544dc82e27afb137de2672a48d726a72fc..9c33bf8370c44ea1ec3b59fcd24f1d5a1765608a 100644 --- a/test/standalone/shared_library/mathtest.zig +++ b/test/standalone/shared_library/mathtest.zig @@ -1,3 +1,5 @@ +export var exported_var: i32 = 9999; + export fn add(a: i32, b: i32) i32 { return a + b; } diff --git a/test/standalone/shared_library/test.c b/test/standalone/shared_library/test.c index f178f78b4583339b65ae51a14fdb58830b0015db..b1b3672d0a48c9c521e98dbdf14f1f52610a9655 100644 --- a/test/standalone/shared_library/test.c +++ b/test/standalone/shared_library/test.c @@ -7,7 +7,16 @@ #include int32_t add(int32_t a, int32_t b); +#if _WIN32 +#define IMPORT __declspec(dllimport) +#else +#define IMPORT +#endif + +extern IMPORT int32_t exported_var; + int main(int argc, char **argv) { assert(add(42, 1337) == 1379); + assert(exported_var == 9999); return 0; } -- 2.54.0 From bd50917c0d86cc45c46cb10868e5e4da6d87c58f Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 72/94] Coff: emit weak externals --- src/link/Coff.zig | 129 ++++++++++++++---- test/link.zig | 32 +++-- .../dynamic-lib-code.implib-windows.dmp | 32 ----- test/link/snapshots/static-lib.llvm.dmp | 15 ++ test/link/snapshots/static-lib.no-llvm.dmp | 12 ++ 5 files changed, 150 insertions(+), 70 deletions(-) delete mode 100644 test/link/snapshots/dynamic-lib-code.implib-windows.dmp create mode 100644 test/link/snapshots/static-lib.llvm.dmp create mode 100644 test/link/snapshots/static-lib.no-llvm.dmp diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 951b3b27cedf408471218c5c341978b04ec7c833..71c157f420b91d57ff5cf8bfd793610f2c20d912 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -847,7 +847,8 @@ pub const Section = struct { pub const GlobalName = struct { name: String, lib_name: String.Optional }; -pub const WeakExternalStrat = enum(u2) { +pub const WeakExternalStrat = enum(u3) { + none, no_library, library, alias, @@ -880,9 +881,8 @@ pub const Symbol = struct { extra_tag: ExtraTag, type: Symbol.Type, dll_storage_class: DllStorageClass, - // Only defined for .alias_si and .alias_name weak_external_strat: WeakExternalStrat, - _: u6 = 0, + _: u5 = 0, }, /// Relocations contained within this symbol loc_relocs: Reloc.Index, @@ -915,12 +915,13 @@ pub const Symbol = struct { /// don't create their own nodes: .input_section, .import_address_table /// Images only. node_offset: u32, - /// This is a weak alias that can replace this symbol - /// Globals only, images only. + /// Images: the weak alias that should replace this symbol if it is not resolved. + /// Objects: he target of a weak external that hasn't been assigned an sti yet. + /// Globals only. weak_alias_si: Symbol.Index, /// For weak externals that have an alias that is also an undef /// external, this is the name of the alias global that should - /// be generated if this symbol is not resolved. + /// be generated and resolved if this symbol is not resolved. /// Globals only, images only. weak_alias_name: String, /// Index of this symbol in the symbol table @@ -2096,6 +2097,7 @@ fn initHeaders( }); } + // TODO: Lazily initialize this instead? coff.import_table.ni = try coff.mf.addLastChildNode( gpa, (try coff.objectSectionMapIndex( @@ -2558,6 +2560,16 @@ pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) s } } +pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.WeakExternalDefinition { + const sti = si.get(coff).value.sti; + if (symbolTableEntryPtr(coff, sti)) |entry| { + assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1); + return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); + } else { + return null; + } +} + pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 { return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)])); } @@ -2599,7 +2611,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { .extra_tag = .size, .type = .unknown, .dll_storage_class = .default, - .weak_external_strat = undefined, + .weak_external_strat = .none, }, .loc_relocs = .none, .target_relocs = .none, @@ -2686,8 +2698,8 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { const GlobalOptions = struct { name: []const u8, - type: Symbol.Type = .unknown, lib_name: ?[]const u8 = null, + type: Symbol.Type = .unknown, dll_storage_class: Symbol.DllStorageClass = .default, }; @@ -2749,13 +2761,14 @@ pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void { assert(!coff.isImage()); const sym = si.get(coff); - assert(sym.ni != .none or sym.gmi != .none); + if (sym.flags.value_tag == .sti and sym.value.sti != .none) + return; + assert(sym.ni != .none or sym.gmi != .none); const gpa = coff.base.comp.gpa; const pending_gop = try coff.symbol_table.pending.getOrPut(gpa, si); - if (!pending_gop.found_existing) { + if (!pending_gop.found_existing) coff.symbol_prog_node.increaseEstimatedTotalItems(1); - } } fn navSection( @@ -3040,15 +3053,20 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void const sym = si.get(coff); assert(sym.ni != .none or sym.gmi != .none); + const existing_sti = switch (sym.flags.value_tag) { + .sti => sym.value.sti, + .weak_alias_si => .none, + else => unreachable, + }; - const entry = coff.symbolTableEntryPtr(sym.value.sti) orelse entry: { + const entry = coff.symbolTableEntryPtr(existing_sti) orelse entry: { var buf: [15]u8 = undefined; const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { const gn = sym.gmi.globalName(coff); break :blk .{ try coff.getOrPutSymbolName(gn.name.toSlice(coff), gn.name), - 0, + @intFromBool(sym.flags.weak_external_strat != .none), if (Symbol.Index.text.get(coff).section_number == sym.section_number) .FUNCTION else @@ -3102,12 +3120,12 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols); const new_num_symbols = old_num_symbols + 1 + num_aux_symbols; - - try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); - coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols); - sym.value.sti = .wrap(old_num_symbols); + try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); + + const old_value = sym.value; + sym.setValue(.{ .sti = .wrap(old_num_symbols) }); si.flushSymbolTableIndex(coff); const entry = coff.symbolTableEntryPtr(sym.value.sti).?; @@ -3118,13 +3136,70 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void .complex_type = complex_type, .base_type = .NULL, }; - entry.storage_class = if (sym.gmi == .none) .STATIC else .EXTERNAL; + + entry.storage_class = if (sym.flags.extra_tag == .next_alias_si) storage: { + // TODO: Could avoid this ordering issue by flushing these in fifo order, instead of lifo + // TODO: Instead keep a map of si -> sti (remove it from sym.value) and walk in forwards order + // Update any existing aux symbols for weak externals that reference this symbol + var any_weak_external = false; + var alias_sym = sym; + while (alias_sym.flags.extra_tag == .next_alias_si) { + const alias_si = alias_sym.extra.next_alias_si; + alias_sym = alias_si.get(coff); + assert(alias_sym.ni == sym.ni); + + if (alias_sym.flags.weak_external_strat != .none) { + switch (alias_sym.flags.value_tag) { + .sti => if (coff.symbolTableWeakExternalAuxEntryPtr(alias_si)) |aux_ptr| + coff.targetStore(&aux_ptr.tag_index, sym.value.sti.unwrap().?), + .weak_alias_si => {}, + else => unreachable, + } + + any_weak_external = true; + } + } + + break :storage if (any_weak_external or sym.gmi != .none) .EXTERNAL else .STATIC; + } else if (sym.gmi == .none) + .STATIC + else + .EXTERNAL; + entry.number_of_aux_symbols = num_aux_symbols; if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry); if (num_aux_symbols > 0) aux_init: { - if (sym.gmi == .none) switch (coff.getNode(sym.ni)) { + if (sym.gmi != .none) { + entry.section_number = .UNDEFINED; + entry.storage_class = .WEAK_EXTERNAL; + + const alias_si = old_value.weak_alias_si; + const alias_sym = alias_si.get(coff); + const tag_index = alias_sym.value.sti.unwrap() orelse tag_index: { + // The alias will update `tag_index` when it is flushed + assert(coff.symbol_table.pending.contains(alias_si)); + break :tag_index 0; + }; + + const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(si).?; + aux_ptr.* = .{ + .tag_index = tag_index, + .flag = switch (sym.flags.weak_external_strat) { + .none => unreachable, + .no_library => .SEARCH_NOLIBRARY, + .library => .SEARCH_LIBRARY, + .alias => .SEARCH_ALIAS, + .anti_dependency => .ANTI_DEPENDENCY, + }, + .unused = @splat(0), + }; + if (coff.targetEndian() != native_endian) + std.mem.byteSwapAllFields(std.coff.SectionDefinition, .@"2", aux_ptr); + + break :aux_init; + } else switch (coff.getNode(sym.ni)) { .image_section => |sec_si| { assert(si == sec_si); const header = sym.section_number.header(coff); @@ -3144,7 +3219,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void break :aux_init; }, else => {}, - }; + } unreachable; } @@ -3153,7 +3228,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }; coff.targetStore(&entry.value, switch (sym.section_number) { - .UNDEFINED => sym.size(), + .UNDEFINED => if (entry.storage_class == .WEAK_EXTERNAL) 0 else sym.size(), .ABSOLUTE, .DEBUG, => unreachable, @@ -6128,6 +6203,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const opt_alt_search_name = coff.alternate_names.get(search_name); const search_libs = if (is_late) switch (sym.flags.value_tag) { .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) { + .none => unreachable, .no_library => false, .library, .alias, @@ -7199,11 +7275,12 @@ fn updateExportsInner( const exported_ni = exported_si.node(coff); const exported_sym = exported_si.get(coff); var prev_alias_si = exported_si; + for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); const name = @"export".opts.name.toSlice(ip); - // TODO: Add an errMsg if this conflicts with an existing global from an input - // first_export_si relies on this being a new symbol. + + // TODO: add an errMsg if this conflicts with an existing global const export_si = try coff.globalSymbol(.{ .name = name, .lib_name = null, @@ -7212,8 +7289,14 @@ fn updateExportsInner( export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; export_sym.section_number = exported_sym.section_number; + if (@"export".opts.linkage == .weak and !coff.isImage()) { + try coff.pendingSymbolTableEntry(exported_si); + export_sym.flags.weak_external_strat = .alias; + export_sym.setValue(.{ .weak_alias_si = exported_si }); + } defer export_si.applyTargetRelocs(coff, .none) catch unreachable; + // The last symbol in the alias list holds the size const prev_alias_sym = prev_alias_si.get(coff); switch (prev_alias_sym.flags.extra_tag) { .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }), diff --git a/test/link.zig b/test/link.zig index baca548bbc44f3b09fbb435b67346411c001029d..ce6d87ab0482af4a2c34cf5a5d20a12916857b9d 100644 --- a/test/link.zig +++ b/test/link.zig @@ -44,7 +44,7 @@ pub fn addCases(ctx: *LinkContext) void { "--elements=file-type", "--symbols", "--only-symbol=foo", - }, .{}); + }, .{ .use_llvm = true }); const exe = case.addExecutable(.{ .name = "test", @@ -192,20 +192,6 @@ pub fn addCases(ctx: *LinkContext) void { "--relocs", }, .{ .arch = true }); - const exe_reloc_err = case.addExecutable(.{ - .name = "test-reloc-err", - .zig_source_bytes = - \\extern const foo: usize; - \\pub fn main() !u8 { - \\ return @intFromBool(foo != 0xcafecafe); - \\} - , - }); - exe_reloc_err.root_module.addObject(abs); - case.expectLinkErrors(exe_reloc_err, .{ - .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/", - }); - const exe = case.addExecutable(.{ .name = "test", .zig_source_bytes = @@ -220,6 +206,22 @@ pub fn addCases(ctx: *LinkContext) void { const run = case.addRunArtifact(exe); run.addCheck(.{ .expect_term = .{ .exited = 0 } }); + + if (!ctx.use_llvm) { + const exe_reloc_err = case.addExecutable(.{ + .name = "test-reloc-err", + .zig_source_bytes = + \\extern const foo: u32; + \\pub fn main() !u8 { + \\ return @intFromBool(foo != 0xcafecafe); + \\} + , + }); + exe_reloc_err.root_module.addObject(abs); + case.expectLinkErrors(exe_reloc_err, .{ + .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/", + }); + } } } diff --git a/test/link/snapshots/dynamic-lib-code.implib-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-windows.dmp deleted file mode 100644 index 850d38a618a3c2dad2c7f15aec0de3b9ace52808..0000000000000000000000000000000000000000 --- a/test/link/snapshots/dynamic-lib-code.implib-windows.dmp +++ /dev/null @@ -1,32 +0,0 @@ - 0 date - 0 user_id - 0 group_id - 0 file_mode -xxxxxxxxxxxxxxxx size - second_linker type - | 7 symbols - | 5 members -xxxxxxxx __imp_foo1 -xxxxxxxx __imp_foo2 -xxxxxxxx foo1 -xxxxxxxx foo2 - 0 version - 8664 machine (AMD64) - 0 time_date_stamp -xxxxxxxxxxxxxxxx size_of_data - 0 hint - CODE import_type - NAME name_type - symbol name | foo1 - import name | foo1 - dll | dynamic-lib-code-lib.dll - 0 version - 8664 machine (AMD64) - 0 time_date_stamp -xxxxxxxxxxxxxxxx size_of_data - 0 hint - CODE import_type - NAME name_type - symbol name | foo2 - import name | foo2 - dll | dynamic-lib-code-lib.dll diff --git a/test/link/snapshots/static-lib.llvm.dmp b/test/link/snapshots/static-lib.llvm.dmp new file mode 100644 index 0000000000000000000000000000000000000000..0b4f7ab2e207befefe8401b67213b0c8e74ab3ed --- /dev/null +++ b/test/link/snapshots/static-lib.llvm.dmp @@ -0,0 +1,15 @@ +lib.lib: COFF archive +lib.lib(obj1.obj): COFF object +xxxx 00000000 1 NULL() EXTERNAL | fooBar +xxxx 00000000 2 NULL EXTERNAL | foo1 +xxxx 00000004 2 NULL EXTERNAL | foo2 +lib.lib(this_is_a_long_name.obj): COFF object +xxxx 00000000 1 NULL() STATIC | this_is_a_long_name.fooWeak +xxxx 00000000 2 NULL STATIC | this_is_a_long_name.foo_strong +xxxx 00000008 2 NULL STATIC | this_is_a_long_name.foo_array +xxxx 00000000 2 NULL EXTERNAL | foo_strong +xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias +xxxx 00000008 2 NULL EXTERNAL | foo_array +xxxx 00000000 UNDEF NULL WEAK_EXTERNAL | fooWeak + | Weak External [falls back to 00000025 via SEARCH_ALIAS] +xxxx 00000000 1 NULL() EXTERNAL | .weak.fooWeak.default.foo_strong diff --git a/test/link/snapshots/static-lib.no-llvm.dmp b/test/link/snapshots/static-lib.no-llvm.dmp new file mode 100644 index 0000000000000000000000000000000000000000..4dcfa8102b537ffbe1a9f7784b556cd7f9984b31 --- /dev/null +++ b/test/link/snapshots/static-lib.no-llvm.dmp @@ -0,0 +1,12 @@ +lib.lib: COFF archive +lib.lib(obj1.obj): COFF object +xxxx 00000000 1 NULL() EXTERNAL | fooBar +xxxx 00000000 2 NULL EXTERNAL | foo1 +xxxx 00000004 2 NULL EXTERNAL | foo2 +lib.lib(this_is_a_long_name.obj): COFF object +xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak + | Weak External [falls back to 00000012 via SEARCH_ALIAS] +xxxx 00000010 2 NULL EXTERNAL | foo_array +xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias +xxxx 00000000 2 NULL EXTERNAL | foo_strong +xxxx 00000000 4 NULL() EXTERNAL | this_is_a_long_name.fooWeak -- 2.54.0 From 537d7b74274c31ea4240870724ffd41e0e5de8dd Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 73/94] Coff: rework symbol table generation Instead of popping from the end of a map of pending symbols, use the map as the storage and flush symbols in order. This simplifies the weak external flow, and keeps the symbol table in a more intuitive order. Also, .sti is no longer in Value, which means that it won't collide with .node_offset if an imported symbol is later exported. --- src/link/Coff.zig | 146 ++++++++++----------- test/link/snapshots/static-lib.no-llvm.dmp | 10 +- 2 files changed, 71 insertions(+), 85 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 71c157f420b91d57ff5cf8bfd793610f2c20d912..f7e859631909c81615d0d2ed4dd90a4604975e0c 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -615,7 +615,8 @@ pub const SymbolTable = struct { ni: MappedFile.Node.Index, strings_ni: MappedFile.Node.Index, strings: std.AutoArrayHashMapUnmanaged(String, StringIndex), - pending: std.AutoArrayHashMapUnmanaged(Symbol.Index, void), + symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, SymbolTable.Index), + pending_symbol_index: u32, // Resizing the symbol table node has the result of accumulating padding // between the last symbol in the symbol table node and the start of the @@ -653,8 +654,8 @@ pub const SymbolTable = struct { none, _, - pub fn wrap(i: ?u32) Index { - return @enumFromInt((i orelse return .none) + 1); + pub fn wrap(i: u32) Index { + return @enumFromInt(i + 1); } pub fn unwrap(sti: Index) ?u32 { @@ -904,13 +905,14 @@ pub const Symbol = struct { }; const ValueTag = enum(u2) { + none, node_offset, weak_alias_si, weak_alias_name, - sti, }; pub const Value = union(ValueTag) { + none, /// The offset of the symbol within its node. Used with symbols that /// don't create their own nodes: .input_section, .import_address_table /// Images only. @@ -924,9 +926,6 @@ pub const Symbol = struct { /// be generated and resolved if this symbol is not resolved. /// Globals only, images only. weak_alias_name: String, - /// Index of this symbol in the symbol table - /// Only used when outputting objects - sti: SymbolTable.Index, }; const ExtraTag = enum(u2) { @@ -1041,6 +1040,11 @@ pub const Symbol = struct { return ni; } + pub fn sti(si: Symbol.Index, coff: *Coff) SymbolTable.Index { + assert(!coff.isImage()); + return coff.symbol_table.symbols.get(si) orelse .none; + } + pub fn next(si: Symbol.Index) Symbol.Index { return @enumFromInt(@intFromEnum(si) + 1); } @@ -1070,7 +1074,7 @@ pub const Symbol = struct { pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void { const sym = si.get(coff); - const index = sym.value.sti.unwrap() orelse return; + const index = si.sti(coff).unwrap().?; var ri = sym.target_relocs; while (ri != .none) { const reloc = ri.get(coff); @@ -1583,7 +1587,8 @@ fn create( .ni = .none, .strings_ni = .none, .strings = .empty, - .pending = .empty, + .symbols = .empty, + .pending_symbol_index = 0, .pending_shrink = false, }, .inputs = .empty, @@ -1667,7 +1672,7 @@ pub fn deinit(coff: *Coff) void { coff.import_table.iat_symbol_indices.deinit(gpa); coff.export_table.entries.deinit(gpa); coff.symbol_table.strings.deinit(gpa); - coff.symbol_table.pending.deinit(gpa); + coff.symbol_table.symbols.deinit(gpa); coff.inputs.deinit(gpa); coff.input_archives.deinit(gpa); coff.input_archive_members.deinit(gpa); @@ -2280,7 +2285,10 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { }); if (!isImage(coff)) { prog_node.increaseEstimatedTotalItems(2); - coff.symbol_prog_node = prog_node.start("Symbols", coff.symbol_table.pending.count()); + coff.symbol_prog_node = prog_node.start( + "Symbols", + coff.symbol_table.symbols.count() - coff.symbol_table.pending_symbol_index, + ); coff.member_prog_node = prog_node.start("Members", coff.pending_members.count()); } coff.input_prog_node = prog_node.start( @@ -2550,8 +2558,7 @@ pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.c return null; } -pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.SectionDefinition { - const sti = si.get(coff).value.sti; +pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.SectionDefinition { if (symbolTableEntryPtr(coff, sti)) |entry| { assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1); return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); @@ -2560,8 +2567,7 @@ pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) s } } -pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.WeakExternalDefinition { - const sti = si.get(coff).value.sti; +pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.WeakExternalDefinition { if (symbolTableEntryPtr(coff, sti)) |entry| { assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1); return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1))); @@ -2604,10 +2610,10 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index { defer coff.symbols.addOneAssumeCapacity().* = .{ .ni = .none, .rva = 0, - .value = .{ .sti = .none }, + .value = .{ .none = {} }, .extra = .{ .size = 0 }, .flags = .{ - .value_tag = .sti, + .value_tag = .none, .extra_tag = .size, .type = .unknown, .dll_storage_class = .default, @@ -2761,14 +2767,14 @@ pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void { assert(!coff.isImage()); const sym = si.get(coff); - if (sym.flags.value_tag == .sti and sym.value.sti != .none) - return; assert(sym.ni != .none or sym.gmi != .none); const gpa = coff.base.comp.gpa; - const pending_gop = try coff.symbol_table.pending.getOrPut(gpa, si); - if (!pending_gop.found_existing) + const gop = try coff.symbol_table.symbols.getOrPut(gpa, si); + if (!gop.found_existing) { coff.symbol_prog_node.increaseEstimatedTotalItems(1); + gop.value_ptr.* = .none; + } } fn navSection( @@ -3047,19 +3053,17 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void { coff.member_prog_node.increaseEstimatedTotalItems(1); } -fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void { +fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void { assert(!coff.isImage()); const gpa = coff.base.comp.gpa; + const si = coff.symbol_table.symbols.keys()[index]; + const sti = &coff.symbol_table.symbols.values()[index]; + const sym = si.get(coff); assert(sym.ni != .none or sym.gmi != .none); - const existing_sti = switch (sym.flags.value_tag) { - .sti => sym.value.sti, - .weak_alias_si => .none, - else => unreachable, - }; - const entry = coff.symbolTableEntryPtr(existing_sti) orelse entry: { + const entry = coff.symbolTableEntryPtr(sti.*) orelse entry: { var buf: [15]u8 = undefined; const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { @@ -3124,11 +3128,10 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf()); - const old_value = sym.value; - sym.setValue(.{ .sti = .wrap(old_num_symbols) }); + sti.* = .wrap(old_num_symbols); si.flushSymbolTableIndex(coff); - const entry = coff.symbolTableEntryPtr(sym.value.sti).?; + const entry = coff.symbolTableEntryPtr(sti.*).?; symbol_name.store(coff, &entry.name); entry.section_number = @enumFromInt(@intFromEnum(sym.section_number)); @@ -3137,34 +3140,19 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void .base_type = .NULL, }; - entry.storage_class = if (sym.flags.extra_tag == .next_alias_si) storage: { - // TODO: Could avoid this ordering issue by flushing these in fifo order, instead of lifo - // TODO: Instead keep a map of si -> sti (remove it from sym.value) and walk in forwards order - // Update any existing aux symbols for weak externals that reference this symbol - var any_weak_external = false; + entry.storage_class = if (sym.gmi != .none) + .EXTERNAL + else if (sym.flags.extra_tag == .next_alias_si) storage: { var alias_sym = sym; - while (alias_sym.flags.extra_tag == .next_alias_si) { + const weak_external = while (alias_sym.flags.extra_tag == .next_alias_si) { const alias_si = alias_sym.extra.next_alias_si; alias_sym = alias_si.get(coff); assert(alias_sym.ni == sym.ni); - - if (alias_sym.flags.weak_external_strat != .none) { - switch (alias_sym.flags.value_tag) { - .sti => if (coff.symbolTableWeakExternalAuxEntryPtr(alias_si)) |aux_ptr| - coff.targetStore(&aux_ptr.tag_index, sym.value.sti.unwrap().?), - .weak_alias_si => {}, - else => unreachable, - } - - any_weak_external = true; - } - } - - break :storage if (any_weak_external or sym.gmi != .none) .EXTERNAL else .STATIC; - } else if (sym.gmi == .none) - .STATIC - else - .EXTERNAL; + if (alias_sym.flags.weak_external_strat != .none) + break true; + } else false; + break :storage if (weak_external) .EXTERNAL else .STATIC; + } else .STATIC; entry.number_of_aux_symbols = num_aux_symbols; if (coff.targetEndian() != native_endian) @@ -3175,15 +3163,8 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void entry.section_number = .UNDEFINED; entry.storage_class = .WEAK_EXTERNAL; - const alias_si = old_value.weak_alias_si; - const alias_sym = alias_si.get(coff); - const tag_index = alias_sym.value.sti.unwrap() orelse tag_index: { - // The alias will update `tag_index` when it is flushed - assert(coff.symbol_table.pending.contains(alias_si)); - break :tag_index 0; - }; - - const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(si).?; + const tag_index = sym.value.weak_alias_si.sti(coff).unwrap().?; + const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(sti.*).?; aux_ptr.* = .{ .tag_index = tag_index, .flag = switch (sym.flags.weak_external_strat) { @@ -3203,7 +3184,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void .image_section => |sec_si| { assert(si == sec_si); const header = sym.section_number.header(coff); - const aux_ptr = coff.symbolTableSectionAuxEntryPtr(si).?; + const aux_ptr = coff.symbolTableSectionAuxEntryPtr(sti.*).?; aux_ptr.* = .{ .length = @intCast(sym.ni.location(&coff.mf).resolve(&coff.mf)[1]), .number_of_relocations = header.number_of_relocations, @@ -3238,7 +3219,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void }, }); - log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.value.sti }); + log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sti.* }); } fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void { @@ -3300,7 +3281,7 @@ fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !S try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.section_table.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); - if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1); const coff_header = coff.headerPtr(); const section_index = coff.targetLoad(&coff_header.number_of_sections); @@ -3643,8 +3624,9 @@ pub fn addReloc( else => |loc_sn| sri: { // The target may not have a node yet, or it could be an extern that will never // have a node. In that case, flushGlobal will create the symbol table entry. - const sti: SymbolTable.Index = if (target.value.sti != .none) - target.value.sti + const existing_sti = target_si.sti(coff); + const sti: SymbolTable.Index = if (existing_sti != .none) + existing_sti else if (target.ni != .none) sti: { try coff.pendingSymbolTableEntry(target_si); break :sti .none; @@ -3669,7 +3651,7 @@ pub fn addReloc( } coff.targetStore(&header.number_of_relocations, new_num_relocations); - if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff))) |aux_ptr| + if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr| coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations); // TODO: These need to allocate from a free list (once deleting relocs is supported) (or can we just remove swap?) @@ -5306,7 +5288,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde .none => { const sec_si = try coff.navSection(zcu, nav.resolved.?); try coff.nodes.ensureUnusedCapacity(gpa, 1); - if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1); const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ .alignment = zcu.navAlignment(nav_index).toStdMem(), .moved = true, @@ -5428,7 +5410,7 @@ fn updateFuncInner( .none => { const sec_si = try coff.navSection(zcu, nav.resolved.?); try coff.nodes.ensureUnusedCapacity(gpa, 1); - if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1); const mod = zcu.navFileScope(func.owner_nav).mod.?; const target = &mod.resolved_target.result; const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ @@ -5902,19 +5884,21 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }; break :task; }; - while (coff.symbol_table.pending.pop()) |pending_si| { - const sym = pending_si.key.get(coff); + if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) { + defer coff.symbol_table.pending_symbol_index += 1; + const si = coff.symbol_table.symbols.keys()[coff.symbol_table.pending_symbol_index]; + const sym = si.get(coff); const sub_prog_node = coff.idleProgNode( tid, coff.symbol_prog_node, if (sym.ni != .none) coff.getNode(sym.ni) else - .{ .import_thunk = pending_si.key.get(coff).gmi }, + .{ .import_thunk = sym.gmi }, ); defer sub_prog_node.end(); coff.flushSymbolTableEntry( - pending_si.key, + coff.symbol_table.pending_symbol_index, .{ .zcu = comp.zcu.?, .tid = tid }, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -5935,7 +5919,7 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.exports_complete and coff.late_globals.items.len > coff.late_globals_pending_index) 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.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) return true; return false; } @@ -6066,7 +6050,7 @@ fn flushUav( .{ .read = true, .initialized = true }, )).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); - if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1); + if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1); const sym = si.get(coff); const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ .alignment = uav_align.toStdMem(), @@ -7012,7 +6996,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { } if (!coff.isImage()) { - if (coff.symbolTableSectionAuxEntryPtr(si)) |aux_ptr| + if (coff.symbolTableSectionAuxEntryPtr(si.sti(coff))) |aux_ptr| coff.targetStore(&aux_ptr.length, @intCast(size)); } }, @@ -7290,6 +7274,8 @@ fn updateExportsInner( export_sym.rva = exported_sym.rva; export_sym.section_number = exported_sym.section_number; if (@"export".opts.linkage == .weak and !coff.isImage()) { + // exported_si needs to be ahead of export_si in the symbol table, + // so that its sti is known when creating the aux entry try coff.pendingSymbolTableEntry(exported_si); export_sym.flags.weak_external_strat = .alias; export_sym.setValue(.{ .weak_alias_si = exported_si }); @@ -7466,10 +7452,10 @@ fn printSymbol( else 0, switch (sym.flags.value_tag) { + .none => "xx", .weak_alias_name => "an", .weak_alias_si => "as", .node_offset => "no", - .sti => "st", }, switch (sym.flags.extra_tag) { .size => "sz", diff --git a/test/link/snapshots/static-lib.no-llvm.dmp b/test/link/snapshots/static-lib.no-llvm.dmp index 4dcfa8102b537ffbe1a9f7784b556cd7f9984b31..0c9a4c1e8254cf6a162a91cef2be6799b7a4ba59 100644 --- a/test/link/snapshots/static-lib.no-llvm.dmp +++ b/test/link/snapshots/static-lib.no-llvm.dmp @@ -4,9 +4,9 @@ xxxx 00000000 1 NULL() EXTERNAL | fooBar xxxx 00000000 2 NULL EXTERNAL | foo1 xxxx 00000004 2 NULL EXTERNAL | foo2 lib.lib(this_is_a_long_name.obj): COFF object -xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak - | Weak External [falls back to 00000012 via SEARCH_ALIAS] -xxxx 00000010 2 NULL EXTERNAL | foo_array -xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias -xxxx 00000000 2 NULL EXTERNAL | foo_strong xxxx 00000000 4 NULL() EXTERNAL | this_is_a_long_name.fooWeak +xxxx 00000000 2 NULL EXTERNAL | foo_strong +xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias +xxxx 00000010 2 NULL EXTERNAL | foo_array +xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak + | Weak External [falls back to 0000000d via SEARCH_ALIAS] -- 2.54.0 From 1b74bc22e969e708d5ac6cb34a7b9cd069b71e80 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Fri, 5 Jun 2026 01:55:37 -0400 Subject: [PATCH 74/94] Rebase fixups - Fixup error sets / use the new linker error conventions - Improve snapshot diff output --- lib/compiler/Maker/Step/Run.zig | 58 +++++++- lib/compiler/objdump.zig | 33 ++--- src/codegen/x86_64/Emit.zig | 3 +- src/link/Coff.zig | 244 ++++++++++++++++++++++---------- src/link/Elf2.zig | 8 +- src/link/MappedFile.zig | 25 ++-- test/src/Link.zig | 2 +- 7 files changed, 262 insertions(+), 111 deletions(-) diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index af4819f62b2717a528ee3823aad1fee269f115c8..35bceb1754d454e89520b9a9330f2b97a00cce43 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -2126,10 +2126,16 @@ fn runCommand( defer gpa.free(snapshot_contents); const result = switch (snapshot.result) { - .stdout => generic_result.stdout.?, .stderr => generic_result.stderr.?, + .stdout => generic_result.stdout.?, }; - if (!mem.eql(u8, snapshot_contents, result)) { + if (std.mem.findDiff(u8, snapshot_contents, result)) |diff_index| { + var diff_line_number: usize = 1; + + for (snapshot_contents[0..diff_index]) |value| { + if (value == '\n') diff_line_number += 1; + } + return step.fail(maker, \\ \\========= snapshot file: ========= @@ -2138,7 +2144,21 @@ fn runCommand( \\{s} \\========= {t} output was: ======== \\{s} - , .{ snapshot.path, snapshot_contents, snapshot.result, result }); + \\================================== + \\first difference on line {d}: + \\expected: + \\{f} + \\found: + \\{f} + , .{ + snapshot.path, + snapshot_contents, + snapshot.result, + result, + diff_line_number, + fmtSnapshotIndicatorLine(snapshot_contents, diff_index), + fmtSnapshotIndicatorLine(result, diff_index), + }); } } }, @@ -2154,6 +2174,38 @@ fn runCommand( } } +const FmtIndicatorLine = struct { + buf: []const u8, + index: usize, +}; + +fn fmtSnapshotIndicatorLine(buf: []const u8, index: usize) std.fmt.Alt( + FmtIndicatorLine, + snapshotIndicatorLine, +) { + return .{ .data = .{ .buf = buf, .index = index } }; +} + +fn snapshotIndicatorLine(line: FmtIndicatorLine, w: *std.Io.Writer) std.Io.Writer.Error!void { + const line_begin_index = if (std.mem.lastIndexOfScalar(u8, line.buf[0..line.index], '\n')) |line_begin| + line_begin + 1 + else + 0; + const line_end_index = if (std.mem.findScalar(u8, line.buf[line.index..], '\n')) |line_end| + (line.index + line_end) + else + line.buf.len; + + try w.writeAll(line.buf[line_begin_index..line_end_index]); + try w.writeByte('\n'); + try w.splatByteAll(' ', line_end_index - line_begin_index); + try w.writeByte('\n'); + if (line.index >= line.buf.len) + try w.writeAll("^ (end of file)") + else + try w.print("^ ('\\x{x:0>2}')\n", .{line.buf[line.index]}); +} + const EvalGenericResult = struct { term: process.Child.Term, stdout: ?[]const u8, diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 0ffb3295fa5834a985d1c91f693077b14bd8dc15..56746f712897835e33531511b5ad456d860c312e 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -1653,10 +1653,10 @@ const coff = struct { fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void { const s = @typeInfo(T).@"struct"; - inline for (s.fields) |flag_field| { - if (flag_field.type == bool and @field(flags, flag_field.name)) { + inline for (s.field_names, s.field_types) |field_name, field_type| { + if (field_type == bool and @field(flags, field_name)) { try w.splatByteAll(' ', cols); - try w.print(fmt, .{flag_field.name}); + try w.print(fmt, .{field_name}); } } } @@ -1693,25 +1693,26 @@ const coff = struct { header: *const T, Custom: type, ) !void { - inline for (@typeInfo(T).@"struct".fields) |field| { - const val = &@field(header, field.name); - if (@hasDecl(Custom, field.name)) { - try @field(Custom, field.name)(d, header); + const s = @typeInfo(T).@"struct"; + inline for (s.field_names, s.field_types) |field_name, field_type| { + const val = &@field(header, field_name); + if (@hasDecl(Custom, field_name)) { + try @field(Custom, field_name)(d, header); } else { - switch (@typeInfo(field.type)) { + switch (@typeInfo(field_type)) { .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{ - .kind = comptime fieldKind(field.name), + .kind = comptime fieldKind(field_name), .width = .{ .explicit = 16 }, - }), field.name }), - .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }), - .@"struct" => |s| { - switch (s.layout) { + }), field_name }), + .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field_name, val.* }), + .@"struct" => |s_field| { + switch (s_field.layout) { .auto, .@"extern", - => try dumpHeader(d, field.type, val, Custom), + => try dumpHeader(d, field_type, val, Custom), .@"packed" => { - try d.w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name }); - try dumpFlags(d.w, "| {s}\n", field.type, val, 15); + try d.w.print("{x: >16} {s}\n", .{ @as(s_field.backing_integer.?, @bitCast(val.*)), field_name }); + try dumpFlags(d.w, "| {s}\n", field_type, val, 15); }, } }, diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index d4677bfe3259fa719e54c4187ef0fb70ae98d79a..1e8e60ffb665fc70709a5291040a32a1bebcb5af 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -155,6 +155,7 @@ pub fn emitMir(emit: *Emit) Error!void { @enumFromInt(try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)) else if (emit.bin_file.cast(.elf2)) |elf| try elf.externSymbol(.{ .name = extern_func.toSlice(&emit.lower.mir).?, + .lib_name = null, .type = .FUNC, }) else if (emit.bin_file.cast(.macho)) |macho_file| @enumFromInt(try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)) @@ -254,7 +255,7 @@ pub fn emitMir(emit: *Emit) Error!void { else => unreachable, } } else if (emit.bin_file.cast(.coff2)) |_| { - if (reloc.target.is_dll_import) switch (lowered_inst.encoding.mnemonic) { + if (target.is_dll_import) switch (lowered_inst.encoding.mnemonic) { .lea => try emit.encodeInst(try .new(.none, .mov, &.{ lowered_inst.ops[0], .{ .mem = .initRip(.ptr, 0) }, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index f7e859631909c81615d0d2ed4dd90a4604975e0c..fe338b3f3ac6bfc4f57d2fbd7954491fdd7a2194 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -94,6 +94,13 @@ pub const imp_prefix = "__imp_"; const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len; +const Error = link.Error || error{MappedFileIo}; +const LoadInputError = Error || + Io.File.SeekError || + Io.File.Reader.SizeError || + Io.Reader.Error || + MappedFile.Error; + /// This is the start of a Portable Executable (PE) file. /// It starts with a MS-DOS header followed by a MS-DOS stub program. /// This data does not change so we include it as follows in all binaries. @@ -484,7 +491,7 @@ pub const Member = struct { longnames, _, - const known_count = @typeInfo(Index).@"enum".fields.len; + const known_count = @typeInfo(Index).@"enum".field_names.len; pub fn get(member_index: Member.Index, coff: *Coff) *Member { return &coff.members.items[@intFromEnum(member_index)]; @@ -2855,7 +2862,7 @@ pub fn getNavVAddr( pt: Zcu.PerThread, nav: InternPool.Nav.Index, reloc_info: link.File.RelocInfo, -) !u64 { +) link.Error!u64 { return coff.getVAddr(reloc_info, try coff.navSymbol(pt.zcu, nav)); } @@ -2863,11 +2870,11 @@ pub fn getUavVAddr( coff: *Coff, uav: InternPool.Index, reloc_info: link.File.RelocInfo, -) !u64 { +) link.Error!u64 { return coff.getVAddr(reloc_info, try coff.uavSymbol(uav)); } -pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) !u64 { +pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) link.Error!u64 { try coff.addReloc( @enumFromInt(@intFromEnum(reloc_info.parent.atom_index)), reloc_info.offset, @@ -3524,19 +3531,13 @@ fn objectSectionMapIndex( const parent_alignment = parent_ni.alignment(&coff.mf); if (alignment.compare(.gt, parent_alignment)) { log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment }); - parent_ni.realign(&coff.mf, gpa, alignment, true) catch |err| switch (err) { - error.Unimplemented => unreachable, - else => |e| return e, - }; + try parent_ni.realign(&coff.mf, gpa, alignment, true); } const old_alignment = sym.ni.alignment(&coff.mf); if (alignment.compare(.gt, old_alignment)) { log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment }); - sym.ni.realign(&coff.mf, gpa, alignment, true) catch |err| switch (err) { - error.Unimplemented => unreachable, - else => |e| return e, - }; + try sym.ni.realign(&coff.mf, gpa, alignment, true); } try coff.verifyParentSectionAttributes( @@ -3561,7 +3562,6 @@ fn verifyParentSectionAttributes( ) !void { if (parent_attrs == child_attrs) return; - const fields = std.meta.fields(ObjectSectionAttributes); const BackingT = @typeInfo(ObjectSectionAttributes).@"struct".backing_integer.?; const num_notes = @popCount(@as(BackingT, @bitCast(parent_attrs)) ^ @as(BackingT, @bitCast(child_attrs))); var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes); @@ -3571,30 +3571,67 @@ fn verifyParentSectionAttributes( parent_name.toSlice(coff), }); - inline for (fields) |field| { - if (@field(child_attrs, field.name) != @field(parent_attrs, field.name)) { + inline for (comptime std.meta.fieldNames(ObjectSectionAttributes)) |field| { + if (@field(child_attrs, field) != @field(parent_attrs, field)) { err.addNote("flags.{s} was {d} in {s}, but {d} in {s}", .{ - field.name, - @intFromBool(@field(child_attrs, field.name)), + field, + @intFromBool(@field(child_attrs, field)), child_name.toSlice(coff), - @intFromBool(@field(parent_attrs, field.name)), + @intFromBool(@field(parent_attrs, field)), parent_name.toSlice(coff), }); } } - return error.LinkFailure; + return error.AlreadyReported; } +const RelocAddend = union(enum) { + known: i64, + /// Relocs tables in input objects don't include the addend. + /// The value needs to be recovered from the reloc location. + pending: void, +}; + pub fn addReloc( coff: *Coff, loc_si: Symbol.Index, offset: u64, target_si: Symbol.Index, - addend: union(enum) { - known: i64, - pending: void, - }, + addend: RelocAddend, + @"type": Reloc.Type, +) link.Error!void { + const diags = &coff.base.comp.link_diags; + try coff.ensureUnusedRelocCapacity(loc_si, 1); + coff.addRelocAssumeCapacity(loc_si, offset, target_si, addend, @"type") catch |err| switch (err) { + error.MappedFileIo => return diags.fail( + "failed to write output file: {t}", + .{coff.mf.io_err.?}, + ), + else => |e| return e, + }; +} + +fn ensureUnusedRelocCapacity(coff: *Coff, loc_si: Symbol.Index, len: usize) !void { + const gpa = coff.base.comp.gpa; + try coff.relocs.ensureUnusedCapacity(gpa, len); + if (isImage(coff)) return; + switch (loc_si.get(coff).section_number) { + .UNDEFINED, .ABSOLUTE, .DEBUG => {}, + else => |loc_sn| { + const section = loc_sn.section(coff); + if (section.relocation_table_ni == .none) + try coff.nodes.ensureUnusedCapacity(gpa, 1); + }, + } +} + +fn addRelocAssumeCapacity( + coff: *Coff, + loc_si: Symbol.Index, + offset: u64, + target_si: Symbol.Index, + addend: RelocAddend, @"type": Reloc.Type, ) !void { const gpa = coff.base.comp.gpa; @@ -3612,8 +3649,6 @@ pub fn addReloc( ri, }); - try coff.relocs.ensureUnusedCapacity(gpa, 1); - const sri: Section.RelocationIndex = if (isImage(coff)) .none else switch (loc_si.get(coff).section_number) { @@ -3638,13 +3673,16 @@ pub fn addReloc( const new_num_relocations = old_num_relocations + 1; const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); if (section.relocation_table_ni == .none) { - try coff.nodes.ensureUnusedCapacity(gpa, 1); - section.relocation_table_ni = try coff.mf.addLastChildNode(gpa, coff.sectionParent(), .{ - .size = new_size, - .alignment = .@"2", - .moved = true, - .resized = true, - }); + section.relocation_table_ni = try coff.mf.addLastChildNode( + gpa, + coff.sectionParent(), + .{ + .size = new_size, + .alignment = .@"2", + .moved = true, + .resized = true, + }, + ); coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn }); } else { try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); @@ -3687,8 +3725,60 @@ pub fn addReloc( target.target_relocs = ri; } -pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError || - Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void { +// pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { +// const diags = &coff.base.comp.link_diags; +// return coff.loadInputInner(input) catch |err| switch (err) { +// else => |e| return e, +// error.MappedFileIo => return diags.fail( +// "failed to write output file: {t}", +// .{coff.mf.io_err.?}, +// ), +// }; +// } + +fn failLoadInput( + coff: *Coff, + err: LoadInputError, + fr: *Io.File.Reader, + path: std.Build.Cache.Path, +) link.Error { + const diags = &coff.base.comp.link_diags; + switch (err) { + else => |e| return e, + error.MappedFileIo => return diags.fail( + "failed to write output file: {t}", + .{coff.mf.io_err.?}, + ), + error.EndOfStream => return diags.failParse( + path, + "unexpected eof", + .{}, + ), + error.AccessDenied, + error.Unexpected, + error.Unseekable, + => |e| return diags.fail( + "failed to read \"{f}\": {t}", + .{ path.fmtEscapeString(), e }, + ), + error.PermissionDenied, + error.SystemResources, + error.Streaming, + => |e| return diags.fail( + "failed to stat \"{f}\": {t}", + .{ path.fmtEscapeString(), e }, + ), + error.ReadFailed => switch (fr.err.?) { + error.Canceled => |e| return e, + else => |e| return diags.fail( + "failed to read \"{f}\": {t}", + .{ path.fmtEscapeString(), e }, + ), + }, + } +} + +pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { const comp = coff.base.comp; const io = comp.io; @@ -3703,32 +3793,24 @@ pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError || var fr = object.file.reader(io, &buf); coff.loadObject(object.path, null, &fr, .{ .offset = fr.logicalPos(), - .size = try fr.getSize(), - }) catch |err| switch (err) { - error.ReadFailed => return fr.err.?, - else => |e| return e, - }; + .size = fr.getSize() catch |err| + return coff.failLoadInput(err, &fr, object.path), + }) catch |err| return coff.failLoadInput(err, &fr, object.path); }, .archive => |archive| { var fr = archive.file.reader(io, &buf); - coff.loadArchive(archive.path, &fr) catch |err| switch (err) { - error.ReadFailed => return fr.err.?, - else => |e| return e, - }; + coff.loadArchive(archive.path, &fr) catch |err| + return coff.failLoadInput(err, &fr, archive.path); }, .res => |res| { var fr = res.file.reader(io, &buf); - coff.loadRes(res.path, &fr) catch |err| switch (err) { - error.ReadFailed => return fr.err.?, - else => |e| return e, - }; + coff.loadRes(res.path, &fr) catch |err| + return coff.failLoadInput(err, &fr, res.path); }, .dso => |dso| { var fr = dso.file.reader(io, &buf); - coff.loadDll(dso.path, &fr) catch |err| switch (err) { - error.ReadFailed => return fr.err.?, - else => |e| return e, - }; + coff.loadDll(dso.path, &fr) catch |err| + return coff.failLoadInput(err, &fr, dso.path); }, .dso_exact => unreachable, } @@ -3771,7 +3853,7 @@ fn loadObject( member_name: ?[]const u8, fr: *Io.File.Reader, fl: MappedFile.Node.FileLocation, -) !void { +) LoadInputError!void { const comp = coff.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -3955,14 +4037,18 @@ fn loadObject( try member.initHeader(coff, path_str, header.time_date_stamp); { - // TODO: This should be deferred to an idle task + // TODO: This should be deferred to an idle task (but resize it here!) var nw: MappedFile.Node.Writer = undefined; member.content_ni.writer(&coff.mf, gpa, &nw); defer nw.deinit(); try fr.seekTo(fl.offset); - if (try nw.interface.sendFileAll(fr, .limited64(fl.size)) != fl.size) - return error.EndOfStream; + const written = nw.interface.sendFileAll(fr, .limited64(fl.size)) catch |err| switch (err) { + error.WriteFailed => return nw.err.?, + else => |e| return e, + }; + + if (written != fl.size) return error.EndOfStream; } break :mi mi; @@ -4816,7 +4902,7 @@ fn failMultipleDefinitions( size: struct { a: u64, b: u64 }, crc: struct { a: u32, b: u32 }, }, -) error{ LinkFailure, OutOfMemory } { +) error{ AlreadyReported, OutOfMemory } { const num_notes: usize = 2 + @as(usize, @intFromBool(comdat_reason != .none)); var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes); try err.addMsg("multiple definitions of '{s}'", .{name.toSlice(coff)}); @@ -4849,7 +4935,7 @@ fn failMultipleDefinitions( ), } - return error.LinkFailure; + return error.AlreadyReported; } const ArchiveMemberHeader = struct { @@ -4889,7 +4975,7 @@ fn parseArchiveMemberHeaderInner( }; } -fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { +fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void { const comp = coff.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -5164,7 +5250,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo } } -fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { +fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void { const comp = coff.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -5177,7 +5263,7 @@ fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { _ = r; } -fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { +fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void { const comp = coff.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -5241,7 +5327,6 @@ pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { errdefer archive.file.close(comp.io); coff.loadInput(.{ .archive = archive }) catch |err| switch (err) { - error.LinkFailure => return, else => |e| return comp.link_diags.failParse( lib.ioi.path(coff), "error loading /DEFAULTLIB library '{s}': {t}", @@ -5265,10 +5350,14 @@ pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void { coff.exports_complete = true; } -pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { +pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void { coff.updateNavInner(pt, nav_index) catch |err| switch (err) { + error.MappedFileIo => return coff.base.cgFail( + nav_index, + "linker failed to update variable: {t}", + .{coff.mf.io_err.?}, + ), else => |e| return e, - error.MappedFileIo => return coff.base.cgFail(nav_index, "linker failed to update variable: {t}", .{coff.mf.io_err.?}), }; } fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -5351,7 +5440,7 @@ pub fn lowerUav( pt: Zcu.PerThread, uav_val: InternPool.Index, uav_align: InternPool.Alignment, -) !link.File.SymbolId { +) link.Error!link.File.SymbolId { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -5380,7 +5469,7 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, -) !void { +) link.Error!void { coff.updateFuncInner(pt, func_index, mir) catch |err| switch (err) { else => |e| return e, error.MappedFileIo => return coff.base.cgFail( @@ -5692,7 +5781,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { } } - return error.LinkFailure; + return error.AlreadyReported; } pub fn flush( @@ -5700,7 +5789,7 @@ pub fn flush( arena: std.mem.Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, -) !void { +) link.Error!void { _ = arena; _ = prog_node; const comp = coff.base.comp; @@ -5723,13 +5812,10 @@ pub fn flush( comp.gpa, number_of_symbols * std.coff.Symbol.sizeOf(), true, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => |e| return comp.link_diags.fail( - "linker failed to compact symbol table: {t}", - .{e}, - ), - }; + ) catch |err| return comp.link_diags.fail( + "linker failed to compact symbol table: {t}", + .{err}, + ); } while (try coff.idle(tid)) {} @@ -7220,10 +7306,14 @@ pub fn updateExports( pt: Zcu.PerThread, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, -) !void { +) link.Error!void { + const diags = &coff.base.comp.link_diags; return coff.updateExportsInner(pt, exported, export_indices) catch |err| switch (err) { - error.OutOfMemory => error.OutOfMemory, - else => |e| coff.base.comp.link_diags.fail("updateExports failed {t}", .{e}) catch error.AnalysisFail, + error.MappedFileIo => return diags.fail( + "failed to write output file: {t}", + .{coff.mf.io_err.?}, + ), + else => |e| return e, }; } fn updateExportsInner( diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 992f36d3f616b4f4a049da28689b00ad5f13e82b..dbed210d8b3c0ae5c24bfa631fa0669fbebb485f 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -3787,7 +3787,7 @@ fn mapInputSection(elf: *Elf, opts: struct { const new_alignment: std.mem.Alignment = .fromByteUnits( std.math.ceilPowerOfTwoAssert(usize, @intCast(opts.addralign)), ); - try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment); + try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment, true); } // ...and update the shdr as needed. switch (elf.shdrPtr(existing_shndx)) { @@ -3950,7 +3950,7 @@ fn uavMapIndex( } else { const node = uav_gop.value_ptr.lsi.index().ptr(elf).node; if (resolved_align.toStdMem().order(node.alignment(&elf.mf)).compare(.gt)) { - try node.realign(&elf.mf, gpa, resolved_align.toStdMem()); + try node.realign(&elf.mf, gpa, resolved_align.toStdMem(), true); } } return umi; @@ -4679,7 +4679,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars // We have a copy relocation for this global, but the amount of space we // reserved for it could be too small or underaligned! try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size); - try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment); + try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, true); const global_ptr = elf.globalByName(name).?; switch (elf.symPtr(global_ptr.symtab_index)) { inline else => |sym_ptr| elf.targetStore(&sym_ptr.size, @intCast(gop.value_ptr.size)), @@ -6762,7 +6762,7 @@ pub fn printNode( elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf), }); }, - .copied_global => |name| try w.print("(copy:{s})", .{name}), + .copied_global => |name| try w.print("(copy:{s})", .{name.slice(elf)}), .nav => |nmi| { const zcu = elf.base.comp.zcu.?; const ip = &zcu.intern_pool; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index a6edeb23a37e4e8a4d7fc4d2f4a82a7a7fcfcd40..3be6d0b49fb2849c231ae7cb9855e86ba6c70008 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -385,13 +385,14 @@ pub const Node = extern struct { /// If the new size can't contain all the children, returns error.ShrinkImpossible. /// If `shift_next` is set, then the following node is shifted backwards into /// the free space as much as alignment allows. + /// Asserts that `size` is >= the end of the last child node. pub fn shrink( ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, size: u64, shift_next: bool, - ) !void { + ) Error!void { try mf.shrinkNode(gpa, ni, size, shift_next); var writers_it = mf.writers.first; while (writers_it) |writer_node| : (writers_it = writer_node.next) { @@ -572,10 +573,7 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { else => |next_ni| { const next_offset, _ = next_ni.location(mf).resolve(mf); if (new_end > next_offset) - mf.realignNode(gpa, next_ni, opts.add_node.alignment, false, false) catch |err| switch (err) { - error.Unimplemented => unreachable, - else => |e| return e, - }; + try next_ni.realign(mf, gpa, opts.add_node.alignment, false); }, } } @@ -724,13 +722,13 @@ fn shrinkNode( const old_offset, _ = node.location().resolve(mf); // This would require unmapping first - if (ni == Node.Index.root) return error.Unimplemented; + assert(ni != Node.Index.root); defer if (std.debug.runtime_safety) mf.verify(); if (node.last != .none) { const last = node.last.get(mf); const last_offset, const last_size = last.location().resolve(mf); - if (last_offset + last_size > size) return error.ShrinkImpossible; + assert(last_offset + last_size > size); } try mf.large.ensureUnusedCapacity(gpa, 4); @@ -757,7 +755,12 @@ fn shrinkNode( node.next.setLocationAssumeCapacity(mf, new_next_offset, next_size); } -fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) (Allocator.Error || Io.Cancelable || IoError)!void { +fn resizeNode( + mf: *MappedFile, + gpa: std.mem.Allocator, + ni: Node.Index, + requested_size: u64, +) (Allocator.Error || Io.Cancelable || IoError)!void { mf.nodes_lock.assertUnlocked(); const io = mf.io; const node = ni.get(mf); @@ -1271,7 +1274,11 @@ fn ensureTotalCapacityPreciseInner(mf: *MappedFile, new_capacity: usize) (Alloca else => |e| return e, } - try mf.memory_map.write(io); + mf.memory_map.write(io) catch |err| switch (err) { + error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking + error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing + else => |e| return e, + }; unmap(mf); } diff --git a/test/src/Link.zig b/test/src/Link.zig index dcb1f0eee613eb0b9d882ccca9ae7ba0051874f3..2836bb1765b49765052488d7988ea7980d28bf77 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -151,7 +151,7 @@ pub const Case = struct { const snapshot_update_path = run_step.captureStdOut(.{}); update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path); } else { - run_step.addCheck(.{ .snapshot = .{ .file = ctx.b.path(snapshot_sub_path) } }); + run_step.addCheck(.{ .expect_stdout_snapshot = ctx.b.path(snapshot_sub_path) }); } ctx.step.dependOn(&run_step.step); -- 2.54.0 From ae41a2b8acfdab6cfd40333f2f188fcc6f3ee2bf Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sun, 7 Jun 2026 02:52:34 -0400 Subject: [PATCH 75/94] Coff: fix .ctor / .dtor generation, the length fields need to be their own nodes, otherwise child nodes will overwrite them test/link: add .ctor / .dtor tests for mingw test/link: include optimize_mode in the target, and only enable .Debug targets for now --- src/link/Coff.zig | 189 ++++++++++++++++++++++++-------------------- test/link.zig | 3 + test/link/mingw.zig | 48 +++++++++++ test/src/Link.zig | 1 - test/tests.zig | 6 +- 5 files changed, 159 insertions(+), 88 deletions(-) create mode 100644 test/link/mingw.zig diff --git a/src/link/Coff.zig b/src/link/Coff.zig index fe338b3f3ac6bfc4f57d2fbd7954491fdd7a2194..8d9b34bded7eaec76bc99db9158578d4243c8107 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -65,8 +65,6 @@ section_merge_pending_index: u32, symbols: std.ArrayList(Symbol), globals: std.array_hash_map.Auto(GlobalName, Symbol.Index), global_pending_index: u32, -late_globals: std.ArrayList(Node.GlobalMapIndex), -late_globals_pending_index: u32, navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index), uavs: std.array_hash_map.Auto(InternPool.Index, Symbol.Index), lazy: std.EnumArray(link.File.LazySymbol.Kind, struct { @@ -188,17 +186,16 @@ pub const Node = union(enum) { archive_member: Member.Index, coff_header, + /// Image only optional_header, - /// Image only data_directories, section_table, - // Archives and objects only + + /// Archives and objects only symbol_table, - // Archives and objects only string_table, - // Archives and objects only relocation_table: Symbol.SectionNumber, relocation_table_entry: Reloc.Index, @@ -220,11 +217,12 @@ pub const Node = union(enum) { pseudo_section: PseudoSectionMapIndex, object_section: ObjectSectionMapIndex, input_section: InputSection.Index, - import_thunk: GlobalMapIndex, // TODO: Rename to import_thunk + import_thunk: GlobalMapIndex, nav: NavMapIndex, uav: UavMapIndex, lazy_code: LazyMapRef.Index(.code), lazy_const_data: LazyMapRef.Index(.const_data), + builtin: Symbol.Index, /// Takes the place of a known node index when that node is not present in the output placeholder, @@ -945,7 +943,7 @@ pub const Symbol = struct { // The size of the symbol size: u32, /// Only valid when .ni == .input_section and .value_tag == .node_offset - /// TODO: This is only used for name lookups, could just be String? + /// TODO: This is only used for name lookups, could just be String, remove `input_symbols`? isli: Node.InputSection.LocalIndex, /// The next symbol in the list of aliases of this symbol. next_alias_si: Symbol.Index, @@ -1322,7 +1320,8 @@ pub const Reloc = extern struct { switch (target_machine) { else => |machine| @panic(@tagName(machine)), .AMD64 => switch (reloc.type.AMD64) { - // TODO: Report these later, in reportUndefs -> reportRelocErrs ? + // TODO: Could wait to report these later, in reportUndefs -> reportRelocErrs, + // so that this function doesn't return an err else => |kind| return coff.base.comp.link_diags.fail( "absolute symbol '{s}' targeted by invalid relocation type: {t}", .{ target_sym.gmi.globalName(coff).name.toSlice(coff), kind }, @@ -1475,8 +1474,10 @@ pub const Reloc = extern struct { pub fn delete(reloc: *Reloc, coff: *Coff) void { if (reloc.sri != .none) { // TODO: Need to remove this from the COFF relocation table (maybe removeswap?) - // TODO: If this was the last reloc causing something to be in the symbol table, we should remove the sti - // That will require flushSymbolTableIndex on the swapped symbol if we exchange indices + // TODO: If this was the last reloc causing something to be in the symbol table, we should remove + // the symbol table entry (and unset sti). That will require flushSymbolTableIndex on the + // swapped symbol if we exchange indices + unreachable; } switch (reloc.prev) { @@ -1623,8 +1624,6 @@ fn create( .symbols = .empty, .globals = .empty, .global_pending_index = 0, - .late_globals = .empty, - .late_globals_pending_index = 0, .navs = .empty, .uavs = .empty, .lazy = .initFill(.{ @@ -1698,7 +1697,6 @@ pub fn deinit(coff: *Coff) void { coff.object_section_table.deinit(gpa); coff.symbols.deinit(gpa); coff.globals.deinit(gpa); - coff.late_globals.deinit(gpa); coff.navs.deinit(gpa); coff.uavs.deinit(gpa); for (&coff.lazy.values) |*lazy| lazy.map.deinit(gpa); @@ -2109,7 +2107,7 @@ fn initHeaders( }); } - // TODO: Lazily initialize this instead? + // TODO: Lazily initialize this instead, avoid the extra logic for this in flushMoved / flushResized coff.import_table.ni = try coff.mf.addLastChildNode( gpa, (try coff.objectSectionMapIndex( @@ -2225,18 +2223,27 @@ pub fn initBuiltins(coff: *Coff) !void { sym.ni = Node.known.header; } + defer coff.flushSectionMerges() catch unreachable; if (coff.isImage() and target.isMinGW() and comp.config.link_libc) { - try coff.symbols.ensureUnusedCapacity(gpa, 6); + try coff.symbols.ensureUnusedCapacity(gpa, 8); try coff.globals.ensureUnusedCapacity(gpa, 2); - try coff.nodes.ensureUnusedCapacity(gpa, 6); + try coff.nodes.ensureUnusedCapacity(gpa, 8); + try coff.section_merges.ensureUnusedCapacity(gpa, 2); const lists: []const struct { global: []const u8, start: String, end: String } = &.{ .{ .global = "__CTOR_LIST__", .start = .@".ctors", .end = .@".ctors$ZZZ" }, .{ .global = "__DTOR_LIST__", .start = .@".dtors", .end = .@".dtors$ZZZ" }, }; + // We need to explicitly merge these into .rdata as in objects they can be marked + // as MEM_WRITE, and would have mismatced section flags. + try coff.section_merges.put(gpa, .@".ctors", .@".rdata"); + try coff.section_merges.put(gpa, .@".dtors", .@".rdata"); + for (lists) |list| { const addr_info = coff.targetAddrInfo(); + + // Any .(c|d)tor$(.*) input sections will merge in between these sections const start_osmi = try coff.objectSectionMapIndex( list.start, addr_info.alignment, @@ -2248,32 +2255,46 @@ pub fn initBuiltins(coff: *Coff) !void { .{ .read = true, .initialized = true }, ); + // Additional nodes are used here, instead of just adding the sentinel + // directly to the section data, since once input sections are added + // as children, they would overwrite that data. const start_sym = start_osmi.symbol(coff).get(coff); - try start_sym.ni.resize(&coff.mf, gpa, addr_info.size); - const start_slice = start_sym.ni.slice(&coff.mf); + const list_len_si = try coff.globalSymbol(.{ .name = list.global, .type = .data }); + const list_len_sym = list_len_si.get(coff); + list_len_sym.setExtra(.{ .size = addr_info.size }); + list_len_sym.ni = try coff.mf.addFirstChildNode(gpa, start_sym.ni, .{ + .size = addr_info.size, + .fixed = true, + }); + coff.nodes.appendAssumeCapacity(.{ .builtin = list_len_si }); + list_len_sym.section_number = start_sym.section_number; + + const start_slice = list_len_sym.ni.slice(&coff.mf); switch (addr_info.magic) { _ => unreachable, inline .PE32, .@"PE32+" => |t| { const addr: *TargetAddr(t) = @ptrCast(@alignCast(start_slice)); // For __CTOR_LIST__ -1 indicates that the list is null terminated. - // For __DTOR_LIST__, this value is ignored. + // For __DTOR_LIST__, this value is ignored, the list is always null terminated coff.targetStore(addr, std.math.maxInt(TargetAddr(t))); }, } - // Any .(c|d)tor$(.*) input sections will merge in between these sections - // TODO: is it guaranteed that there will be no padding between those nodes? - const end_sym = end_osmi.symbol(coff).get(coff); - try end_sym.ni.resize(&coff.mf, gpa, addr_info.size); - @memset(end_sym.ni.slice(&coff.mf), 0); + const list_end_si = coff.addSymbolAssumeCapacity(); + const list_end_sym = list_end_si.get(coff); + list_end_sym.setExtra(.{ .size = addr_info.size }); + list_end_sym.ni = try coff.mf.addFirstChildNode(gpa, end_sym.ni, .{ + .size = addr_info.size, + .fixed = true, + }); + coff.nodes.appendAssumeCapacity(.{ .builtin = list_end_si }); + list_end_sym.section_number = start_sym.section_number; - const list_si = try coff.globalSymbol(.{ .name = list.global, .type = .data }); - const list_sym = list_si.get(coff); - list_sym.ni = start_sym.ni; - list_sym.section_number = start_sym.section_number; + @memset(list_end_sym.ni.slice(&coff.mf), 0); - start_sym.setExtra(.{ .next_alias_si = list_si }); + try list_len_si.flushMoved(coff); + try list_end_si.flushMoved(coff); } } } @@ -2284,7 +2305,6 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { coff.synth_prog_node = prog_node.start("Synthetics", count: { var count = coff.globals.count() - coff.global_pending_index + - coff.late_globals.items.len - coff.late_globals_pending_index + coff.section_merges.count() - coff.section_merge_pending_index; for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index; @@ -2344,6 +2364,7 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { .relocation_table, .relocation_table_entry, .input_section, + .builtin, => unreachable, .image_section => |si| si, .import_directory_table => break :parent_rva coff.targetLoad( @@ -2404,7 +2425,7 @@ pub inline fn targetEndian(_: *const Coff) std.lang.Endian { } fn targetAddrInfo(coff: *Coff) struct { - size: u64, + size: u8, alignment: std.mem.Alignment, magic: std.coff.OptionalHeader.Magic, } { @@ -3450,9 +3471,9 @@ fn pseudoSectionMapIndex( } else pseudo_section_gop.value_ptr.get(coff).section_number; try coff.verifyParentSectionAttributes( + parent_sn, + name, .pseudo, - parent_sn.name(coff), - name, .fromFlags(parent_sn.header(coff).flags), attributes, ); @@ -3478,6 +3499,7 @@ fn objectSectionMapIndex( ) !Node.ObjectSectionMapIndex { const gpa = coff.base.comp.gpa; const name_slice = name.toSlice(coff); + // TODO: Should this be a section merge instead? const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name_slice, ".tls")) attr: { // In images, the .tls section is a read-only template var attr = attributes; @@ -3541,9 +3563,9 @@ fn objectSectionMapIndex( } try coff.verifyParentSectionAttributes( + sym.section_number, + name, .object, - sym.section_number.name(coff), - name, .fromFlags(sym.section_number.header(coff).flags), effective_attributes, ); @@ -3554,21 +3576,34 @@ fn objectSectionMapIndex( // TODO: Include align in attrs and verify the current align is >= requested fn verifyParentSectionAttributes( coff: *Coff, - kind: enum { pseudo, object }, - parent_name: String, + parent: Symbol.SectionNumber, child_name: String, + child_kind: enum { pseudo, object }, parent_attrs: ObjectSectionAttributes, child_attrs: ObjectSectionAttributes, ) !void { if (parent_attrs == child_attrs) return; + const was_merged = switch (child_kind) { + .pseudo => coff.section_merges.contains(child_name), + .object => if (coff.getString( + coff.objectSectionParentName(child_name.toSlice(coff)), + ).unwrap()) |pseudo_name| + coff.section_merges.contains(pseudo_name) + else + false, + }; + + // The section was intentionally merged by the user or builtin rule + if (was_merged) return; + const BackingT = @typeInfo(ObjectSectionAttributes).@"struct".backing_integer.?; const num_notes = @popCount(@as(BackingT, @bitCast(parent_attrs)) ^ @as(BackingT, @bitCast(child_attrs))); var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes); try err.addMsg("{t} section '{s}' was placed in parent section '{s}' with mismatched flags", .{ - kind, + child_kind, child_name.toSlice(coff), - parent_name.toSlice(coff), + parent.name(coff).toSlice(coff), }); inline for (comptime std.meta.fieldNames(ObjectSectionAttributes)) |field| { @@ -3578,7 +3613,7 @@ fn verifyParentSectionAttributes( @intFromBool(@field(child_attrs, field)), child_name.toSlice(coff), @intFromBool(@field(parent_attrs, field)), - parent_name.toSlice(coff), + parent.name(coff).toSlice(coff), }); } } @@ -4094,6 +4129,7 @@ fn loadObject( // Discover symbol names and COMDAT symbol mappings var symbol_i: u32 = 0; + var num_included_symbols: u32 = 0; while (symbol_i < header.number_of_symbols) { var symbol: std.coff.Symbol = undefined; @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], try r.take(symbol_size)); @@ -4275,6 +4311,9 @@ fn loadObject( }; for (values, 0..) |value, i| { + if (section_number == .ABSOLUTE) + num_included_symbols += 1; + switch (value) { .section => {}, .static, @@ -4545,12 +4584,10 @@ fn loadObject( }; } - while (coff.section_merge_pending_index < coff.section_merges.count()) : (coff.section_merge_pending_index += 1) - try coff.flushSectionMerge(coff.section_merge_pending_index); + try coff.flushSectionMerges(); // Resolve pending associations, create parent sections var num_included_sections: u16 = 0; - var num_included_symbols: u32 = 0; var num_included_relocs: u32 = 0; for (sections) |*section| { comdat: switch (section.comdat_result) { @@ -5916,22 +5953,6 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { }) coff.global_pending_index += 1; break :task; } - if (coff.exports_complete and coff.late_globals_pending_index < coff.late_globals.items.len) { - const gmi: Node.GlobalMapIndex = coff.late_globals.items[coff.late_globals_pending_index]; - const sub_prog_node = coff.synth_prog_node.start( - gmi.globalName(coff).name.toSlice(coff), - 0, - ); - defer sub_prog_node.end(); - if (coff.flushGlobal(gmi) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - else => |e| return comp.link_diags.fail( - "linker failed to lower constant: {t}", - .{e}, - ), - }) coff.late_globals_pending_index += 1; - break :task; - } if (coff.exports_complete and coff.pending_special_symbol != .none) { coff.pending_special_symbol = coff.flushSpecialSymbol(coff.pending_special_symbol) catch |err| switch (err) { @@ -6002,7 +6023,6 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.pending_input != null) return true; if (coff.exports_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.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_symbol_index < coff.symbol_table.symbols.count()) return true; @@ -6223,11 +6243,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const gpa = comp.gpa; const gn = gmi.globalName(coff); const si = gmi.symbol(coff); - const is_late = gmi.unwrap().? < coff.global_pending_index; log.debug( - "flushGlobal({s}, {?s}, {}) = n{d} {d}@{d}", - .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), is_late, si.get(coff).ni, si, si.get(coff).section_number }, + "flushGlobal({s}, {?s}) = n{d} {d}@{d}", + .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), si.get(coff).ni, si, si.get(coff).section_number }, ); if (!coff.isImage()) { @@ -6271,7 +6290,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { }; const opt_alt_search_name = coff.alternate_names.get(search_name); - const search_libs = if (is_late) switch (sym.flags.value_tag) { + const search_libs = switch (sym.flags.value_tag) { .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) { .none => unreachable, .no_library => false, @@ -6285,18 +6304,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { ), }, else => true, - } else search_libs: { - if (switch (sym.flags.value_tag) { - .weak_alias_si, .weak_alias_name => true, - else => opt_alt_search_name != null, - }) { - // We need to wait until all exports are known before resolving these - coff.synth_prog_node.increaseEstimatedTotalItems(1); - (try coff.late_globals.addOne(gpa)).* = gmi; - return true; - } - - break :search_libs true; }; const opt_indices_lists: []const ?InputArchive.SearchList = if (search_libs) &.{ @@ -6381,12 +6388,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { switch (sym.flags.value_tag) { .weak_alias_si => { - assert(is_late); try coff.aliasGlobal(gmi, sym.value.weak_alias_si); return true; }, .weak_alias_name => { - assert(is_late); // Convert an unresolved weak external that itself refers to an undef external // into a (possibly new) global, so it can be resolved separately. const alias_gop = try coff.getOrPutGlobalSymbol(.{ @@ -6400,7 +6405,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { // If there was an object that had the alternate name, we've attempted to load it if (opt_alt_search_name) |alt_search_name| { - assert(is_late); if (coff.globals.get(.{ .name = alt_search_name, .lib_name = .none })) |alias_si| { try coff.aliasGlobal(gmi, alias_si); return true; @@ -6986,6 +6990,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { .lazy_code, .lazy_const_data, => |mi| try mi.symbol(coff).flushMoved(coff), + .builtin => |si| try si.flushMoved(coff), } try ni.childrenMoved(coff.base.comp.gpa, &coff.mf); } @@ -7126,8 +7131,10 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { .uav, .lazy_code, .lazy_const_data, + .builtin, => {}, - .placeholder => unreachable, + .placeholder, + => unreachable, } } @@ -7217,6 +7224,11 @@ fn flushExportsSort(coff: *Coff) void { }); } +fn flushSectionMerges(coff: *Coff) !void { + while (coff.section_merge_pending_index < coff.section_merges.count()) : (coff.section_merge_pending_index += 1) + try coff.flushSectionMerge(coff.section_merge_pending_index); +} + fn flushSectionMerge(coff: *Coff, index: u32) !void { assert(coff.isImage()); const from = coff.section_merges.keys()[index]; @@ -7237,7 +7249,6 @@ fn flushSectionMerge(coff: *Coff, index: u32) !void { // This is non-trivial as we can't leave holes in the section table. // TODO: Merge section flags _ = to_sym; - return coff.base.comp.link_diags.fail("TODO implement section to section merge", .{}); } else if (coff.pseudo_section_table.get(to)) |to_ps_si| { const to_sym = to_ps_si.get(coff); @@ -7265,7 +7276,6 @@ fn flushSectionMerge(coff: *Coff, index: u32) !void { // TODO: Move from_psmi's node into to_sec // TODO: Update .section_number for all contained syms // TODO: Merge section flags - return coff.base.comp.link_diags.fail("TODO implement pseudosection to section merge", .{}); } else if (coff.pseudo_section_table.get(to)) |to_ps_si| { const to_sym = to_ps_si.get(coff); @@ -7273,7 +7283,6 @@ fn flushSectionMerge(coff: *Coff, index: u32) !void { return; // TODO: Same as above, but move from_psmi's node after to_psmi's node in its parent - return coff.base.comp.link_diags.fail("TODO implement pseudosection to pseudosection merge", .{}); } @@ -7626,7 +7635,8 @@ fn printNodeName( inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{ smi.name(coff).toSlice(coff), }), - .import_thunk => |gmi| { + .import_thunk, + => |gmi| { const gn = gmi.globalName(coff); try w.writeByte('('); if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); @@ -7655,6 +7665,15 @@ fn printNodeName( .tid = tid, }), }), + .builtin => |si| { + const sym = si.get(coff); + if (sym.gmi != .none) { + const gn = sym.gmi.globalName(coff); + try w.writeByte('('); + if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); + try w.print("{s})", .{gn.name.toSlice(coff)}); + } + }, } } diff --git a/test/link.zig b/test/link.zig index ce6d87ab0482af4a2c34cf5a5d20a12916857b9d..557ee146f68d3668e1579efa5a2a3cf16db19eb0 100644 --- a/test/link.zig +++ b/test/link.zig @@ -1,4 +1,7 @@ pub fn addCases(ctx: *LinkContext) void { + if (ctx.target.result.isMinGW()) + @import("link/mingw.zig").addCases(ctx); + if (ctx.includeTest("static-lib")) |case| { const obj1 = case.addObject(.{ .name = "obj1", diff --git a/test/link/mingw.zig b/test/link/mingw.zig new file mode 100644 index 0000000000000000000000000000000000000000..e584709de04f5fc21ac2283d2356226c4c0ec36c --- /dev/null +++ b/test/link/mingw.zig @@ -0,0 +1,48 @@ +pub fn addCases(ctx: *LinkContext) void { + if (ctx.includeTest("ctor-dtor")) |case| { + if (!ctx.link_libc) return; + + const obj = case.addObject(.{ + .name = "obj", + .use_llvm = true, + .use_lld = true, + .c_source_bytes = + \\#include + \\int foo; + \\__attribute__((constructor)) + \\static void init_foo() { + \\ foo = 42; + \\} + \\__attribute__((destructor)) + \\static void deinit_foo() { + \\ exit(42); + \\} + , + }); + + const lib = case.addLibrary(.static, .{ + .name = "lib", + .name_prefix = false, + .name_target = false, + }); + lib.root_module.addObject(obj); + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = + \\extern var foo: u32; + \\pub fn main() !u8 { + \\ if (foo != 42) return 1; + \\ return 2; + \\} + , + }); + exe.root_module.addObject(obj); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 42 } }); + } +} + +const LinkContext = @import("../tests.zig").LinkContext; +const std = @import("std"); diff --git a/test/src/Link.zig b/test/src/Link.zig index 2836bb1765b49765052488d7988ea7980d28bf77..16c7dbdf83ace21ba7d92ef0c88ba54e0efb42fa 100644 --- a/test/src/Link.zig +++ b/test/src/Link.zig @@ -117,7 +117,6 @@ pub const Case = struct { /// contains the expected output. Snapshots alias between all build /// configurations by default, but by specifying fields in `scope`, /// unique snapshot names are generated for each value of that field. - /// pub fn verifyObjdump( self: *const Case, file: Build.LazyPath, diff --git a/test/tests.zig b/test/tests.zig index 2159d8a8a05ea45b900ed4beec64141ddb9624cf..75810c0e17042634e335a1712c46338636987501 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2063,6 +2063,7 @@ const c_abi_targets = blk: { const LinkTarget = struct { target: std.Target.Query = .{}, + optimize_mode: std.builtin.OptimizeMode = .Debug, link_libc: bool = false, use_llvm: bool = false, use_lld: bool = false, @@ -3207,9 +3208,10 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step { } for (options.optimize_modes) |optimize_mode| { - const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode); - if (options.skip_llvm and would_use_llvm) continue; + if (link_target.optimize_mode != optimize_mode) continue; if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue; + const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode); + if (options.skip_llvm and would_use_llvm) continue; const opt_update_step = if (update_snapshots) update: { const update_step = Step.UpdateSourceFiles.create(b); -- 2.54.0 From 0bacbd4daac01489eb4d143e81cb52c154122059 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sun, 7 Jun 2026 23:32:55 -0400 Subject: [PATCH 76/94] test/link: add tls test --- test/link.zig | 51 +++++++++++++++++++++++++++++++++++++ test/link/snapshots/tls.dmp | 7 +++++ 2 files changed, 58 insertions(+) create mode 100644 test/link/snapshots/tls.dmp diff --git a/test/link.zig b/test/link.zig index 557ee146f68d3668e1579efa5a2a3cf16db19eb0..5abc2e38231911734578160c0eed572b07899551 100644 --- a/test/link.zig +++ b/test/link.zig @@ -72,6 +72,57 @@ pub fn addCases(ctx: *LinkContext) void { run.addCheck(.{ .expect_term = .{ .exited = 0 } }); } + if (ctx.includeTest("tls")) |case| { + const obj = case.addObject(.{ + .name = "obj", + .zig_source_bytes = + \\threadlocal var threadlocal_var: u32 = 1234; + \\threadlocal var threadlocal_arr: [4]u16 = .{ 0x1111, 0x2222, 0x3333, 0x4444, }; + \\export fn threadlocal_read(a: *u32, b: *u16) void { + \\ a.* = threadlocal_var; + \\ b.* = threadlocal_arr[3]; + \\} + \\export fn threadlocal_write(a: u32, b: u16) void { + \\ threadlocal_var = a; + \\ threadlocal_arr[3] = b; + \\} + , + }); + + case.verifyObjdump(obj.getEmittedBin(), &.{ + "-s", + "--symbols", + "--only-symbol=threadlocal", + "--only-symbol=tls", + }, .{}); + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = + \\extern fn threadlocal_read(a: *u32, b: *u16) void; + \\extern fn threadlocal_write(a: u32, b: u16) void; + \\threadlocal var threadlocal_foo: u64 = 0xcafecafecafecafe; + \\pub fn main() !u8 { + \\ var a: u32 = undefined; + \\ var b: u16 = undefined; + \\ threadlocal_read(&a, &b); + \\ if (a != 1234 or b != 0x4444) return 1; + \\ if (threadlocal_foo != 0xcafecafecafecafe) return 2; + \\ threadlocal_write(0xabcdabcd, 0x5555); + \\ threadlocal_foo = 1; + \\ threadlocal_read(&a, &b); + \\ if (a != 0xabcdabcd or b != 0x5555) return 3; + \\ if (threadlocal_foo != 1) return 4; + \\ return 0; + \\} + , + }); + exe.root_module.addObject(obj); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 0 } }); + } + if (ctx.includeTest("dynamic-lib-code")) |case| { const lib = case.addLibrary(.dynamic, .{ .name = "lib", diff --git a/test/link/snapshots/tls.dmp b/test/link/snapshots/tls.dmp new file mode 100644 index 0000000000000000000000000000000000000000..02ae0d78df3fa5cddafaed1d3dc49a50debf7247 --- /dev/null +++ b/test/link/snapshots/tls.dmp @@ -0,0 +1,7 @@ +xxxx 00000000 5 NULL STATIC | .tls$ + | Section [size xxxxxxxx chksum 00000000 relocs 0000 lines 0000] +xxxx 00000000 5 NULL STATIC | obj.threadlocal_var +xxxx 00000008 5 NULL STATIC | obj.threadlocal_arr +xxxx 00000000 UNDEF NULL EXTERNAL | _tls_index +xxxx 00000000 4 NULL() EXTERNAL | threadlocal_write +xxxx 00000060 4 NULL() EXTERNAL | threadlocal_read -- 2.54.0 From c0b07b144bcce98c25c67d1f91e49d1cb92be1cd Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sun, 7 Jun 2026 23:40:46 -0400 Subject: [PATCH 77/94] test/link: fixup tls snapshots --- test/link.zig | 2 +- test/link/snapshots/tls.llvm.dmp | 9 +++++++++ test/link/snapshots/{tls.dmp => tls.no-llvm.dmp} | 0 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 test/link/snapshots/tls.llvm.dmp rename test/link/snapshots/{tls.dmp => tls.no-llvm.dmp} (100%) diff --git a/test/link.zig b/test/link.zig index 5abc2e38231911734578160c0eed572b07899551..c21030db764a2b650f9e80e51514d7a2490d7c40 100644 --- a/test/link.zig +++ b/test/link.zig @@ -94,7 +94,7 @@ pub fn addCases(ctx: *LinkContext) void { "--symbols", "--only-symbol=threadlocal", "--only-symbol=tls", - }, .{}); + }, .{ .use_llvm = true }); const exe = case.addExecutable(.{ .name = "test", diff --git a/test/link/snapshots/tls.llvm.dmp b/test/link/snapshots/tls.llvm.dmp new file mode 100644 index 0000000000000000000000000000000000000000..8e70edb3acc79d3559d05451ab555d01d621d26a --- /dev/null +++ b/test/link/snapshots/tls.llvm.dmp @@ -0,0 +1,9 @@ +xxxx 00000000 6 NULL STATIC | .tls$ + | Section [size xxxxxxxx chksum a194a569 relocs 0000 lines 0000] +xxxx 00000000 1 NULL() STATIC | obj.threadlocal_write +xxxx 00000000 UNDEF NULL EXTERNAL | _tls_index +xxxx 00000000 6 NULL STATIC | obj.threadlocal_var +xxxx 00000004 6 NULL STATIC | obj.threadlocal_arr +xxxx 00000040 1 NULL() STATIC | obj.threadlocal_read +xxxx 00000000 1 NULL() EXTERNAL | threadlocal_write +xxxx 00000040 1 NULL() EXTERNAL | threadlocal_read diff --git a/test/link/snapshots/tls.dmp b/test/link/snapshots/tls.no-llvm.dmp similarity index 100% rename from test/link/snapshots/tls.dmp rename to test/link/snapshots/tls.no-llvm.dmp -- 2.54.0 From c7bb3a39b781fdecb74ae8df0537516ed1d31826 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Mon, 8 Jun 2026 00:27:32 -0400 Subject: [PATCH 78/94] Coff: TODO cleanup --- src/link/Coff.zig | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 8d9b34bded7eaec76bc99db9158578d4243c8107..b4760a84dcb1315dbc2cf572286fd8d24d003e40 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -199,7 +199,7 @@ pub const Node = union(enum) { relocation_table: Symbol.SectionNumber, relocation_table_entry: Reloc.Index, - image_section: Symbol.Index, // TODO: rename image_section -> section + image_section: Symbol.Index, /// Images only import_directory_table, @@ -603,7 +603,7 @@ pub const LongNamesTable = struct { coff: *Coff, pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool { - assert(adapter.coff.isArchive()); // TODO: move to helper that uses this + assert(adapter.coff.isArchive()); const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf); const rhs = adapter.coff.long_names_table.entries.values()[rhs_index]; return std.mem.eql(u8, longnames_slice[rhs.offset..][0..rhs.len], lhs_key); @@ -763,7 +763,6 @@ pub const ImportTable = struct { }; pub const String = enum(u32) { - // TODO: Re-order @".data" = 0, @".idata" = 6, @".rdata" = 13, @@ -943,7 +942,6 @@ pub const Symbol = struct { // The size of the symbol size: u32, /// Only valid when .ni == .input_section and .value_tag == .node_offset - /// TODO: This is only used for name lookups, could just be String, remove `input_symbols`? isli: Node.InputSection.LocalIndex, /// The next symbol in the list of aliases of this symbol. next_alias_si: Symbol.Index, @@ -2919,7 +2917,6 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, const gpa = comp.gpa; // TODO: These two nodes could to be inside a movable node if kind == .coff|.import - const header_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{ .size = @sizeOf(std.coff.ArchiveMemberHeader), .alignment = .@"2", @@ -3573,7 +3570,6 @@ fn objectSectionMapIndex( return osmi; } -// TODO: Include align in attrs and verify the current align is >= requested fn verifyParentSectionAttributes( coff: *Coff, parent: Symbol.SectionNumber, @@ -3727,7 +3723,7 @@ fn addRelocAssumeCapacity( if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr| coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations); - // TODO: These need to allocate from a free list (once deleting relocs is supported) (or can we just remove swap?) + // TODO: These need to allocate from a free list, once deleting relocs is supported const sri: Section.RelocationIndex = .wrap(old_num_relocations); const entry = sri.entry(coff, loc_sn).?; if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index); @@ -4509,7 +4505,7 @@ fn loadObject( }); // TODO: What if the same symbol is incorrectly defined twice in this obj? - // TODO: Would need to mark this global as pending, or notice it later when .ni != none + // Would need to mark this global as pending, or notice it later when .ni != none if (!global_gop.found_existing or global_gop.value_ptr.get(coff).ni == .none) { symbol.si = global_gop.value_ptr.*; break :comdat .include; @@ -4554,9 +4550,7 @@ fn loadObject( const sym = si.get(coff); const existing_crc = switch (coff.getNode(sym.ni)) { .input_section => |isi| isi.inputSection(coff).crc, - // TODO: Should this result be cached somewhere? - // TODO: Is this slice triggering has_content = true un-necessarily? Check section for init data flag. - else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)), + else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.sliceConst(&coff.mf)), }; if (existing_crc == section.comdat_crc) { @@ -4611,7 +4605,7 @@ fn loadObject( .pending => unreachable, } - // TODO: Until we support sorting .pdata, we shouldn't merge these in, the result would be invalid + // Until we support sorting .pdata, we shouldn't merge these in, the result would be invalid const section_name = section.name.toSlice(coff); if (std.mem.startsWith(u8, section_name, ".pdata")) continue; @@ -4995,7 +4989,6 @@ fn parseArchiveMemberHeader( }; } -// TODO: Move to std.coff? fn parseArchiveMemberHeaderInner( header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8, @@ -5274,7 +5267,6 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) Loa }; } else { member.content.object.size = res.size; - // TODO: If .UNKNOWN assert later that it contains no non-undef symbols? // Microsoft's CRT contains members that set .UNKNOWN but do have undef symbols if (machine != expected_machine and machine != .UNKNOWN) { return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{ @@ -5859,7 +5851,6 @@ pub fn flush( if (coff.isImage()) try coff.reportUndefs(tid); - // Implib generation should instead be done via building a MappedFile progressively if (comp.emit_implib) |implib_file| coff.flushImplib(implib_file) catch |err| return comp.link_diags.fail("flushing implib '{s}' failed: {t}", .{ implib_file, err }); @@ -6080,9 +6071,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { try coff.flushMember(pending_mi.key); break :task; } - // TODO: All the sort / shrink tasks ideally run only once - otherwise it's wasteful - // Defer until exports_complete? - if (coff.export_table.pending_sort) { + if (coff.exports_complete and coff.export_table.pending_sort) { defer coff.export_table.pending_sort = false; const sub_prog_node = coff.idleProgNode( tid, @@ -6098,7 +6087,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.input_sections.items.len > coff.input_section_pending_index) return true; if (coff.mf.updates.items.len > 0) return true; if (coff.pending_members.count() > 0) return true; - if (coff.export_table.pending_sort) return true; + if (coff.exports_complete and coff.export_table.pending_sort) return true; return false; } @@ -6578,8 +6567,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*); switch (import.kind) { .iat_ptr => { - // TODO: Currently the codegen is wrong for loading the address of these globals, - // we generate lea [] when it should be mov [] const iat_sym = gop.value_ptr.import_address_table_si.get(coff); sym.section_number = iat_sym.section_number; sym.ni = iat_sym.ni; @@ -7166,7 +7153,6 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void { }; // TODO: Does this sort need to also sort by linker input order (if names equal)? - std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{ .coff = coff, .indices = coff.secondLinkerMemberIndicesSlice(), @@ -7493,7 +7479,7 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe _ = exported; _ = name; - // TODO: Delete from first / second linker member table (remove swap?) + // TODO: Delete from first / second linker member table // TODO: Delete from symbol table inside section } @@ -7609,7 +7595,6 @@ fn printNodeName( .input_section => |isi| { const ioi = isi.input(coff); const is = isi.inputSection(coff); - // TODO: Use only filename from these paths, they are long try w.print("({f}{f}, {s}", .{ ioi.path(coff).fmtEscapeString(), fmtMemberNameString(ioi.memberName(coff)), -- 2.54.0 From 38d06fdf0e9b9eb2831675b550e3ba7e41b74908 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Mon, 8 Jun 2026 00:46:25 -0400 Subject: [PATCH 79/94] MappedFile: doc fixup test/standalone/shared_library: remove install step --- src/link/MappedFile.zig | 2 +- test/standalone/shared_library/build.zig | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 3be6d0b49fb2849c231ae7cb9855e86ba6c70008..5c2892ecaa245f62974b132e979a3c68940bfa56 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -382,7 +382,7 @@ pub const Node = extern struct { } /// Shrink a node to `size`, exactly. - /// If the new size can't contain all the children, returns error.ShrinkImpossible. + /// Asserts that the new size can contain all the children. /// If `shift_next` is set, then the following node is shifted backwards into /// the free space as much as alignment allows. /// Asserts that `size` is >= the end of the last child node. diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 478fcd501704cacb21081cc7f463fe8fc1a386c3..a7a55c28a20b97f8493a964d468fe4477d82c444 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -76,9 +76,6 @@ pub fn build(b: *std.Build) void { }); exe.root_module.linkLibrary(lib); - b.getInstallStep().dependOn(&b.addInstallArtifact(lib, .{}).step); - b.getInstallStep().dependOn(&b.addInstallArtifact(exe, .{}).step); - const run_cmd = b.addRunArtifact(exe); test_step.dependOn(&run_cmd.step); } -- 2.54.0 From a9b0999d5b8922580560fcb0f65f5860df78c8be Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 9 Jun 2026 22:17:13 -0400 Subject: [PATCH 80/94] Coff: a few fixups for incremental - Handle exports being updated - Track free relocs --- src/link/Coff.zig | 91 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 27 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index b4760a84dcb1315dbc2cf572286fd8d24d003e40..eba5d6222cdfad3507d488a5f0084f882f3131fa 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -75,6 +75,8 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct { alignment: InternPool.Alignment, }), relocs: std.ArrayList(Reloc), +first_free_reloc: Reloc.Index, +last_free_reloc: Reloc.Index, const_prog_node: std.Progress.Node, synth_prog_node: std.Progress.Node, symbol_prog_node: std.Progress.Node, @@ -1147,10 +1149,14 @@ pub const Reloc = extern struct { loc: Symbol.Index, target: Symbol.Index, flags: packed struct(u8) { - // Indicates the addend is not known and should be recovered from the location itself. - // COFF relocation tables don't encode the addend, only the location. + /// Indicates the addend is not known and should be recovered from the location itself. + /// COFF relocation tables don't encode the addend, only the location. recover_addend: bool, - _: u7 = 0, + /// Set if this reloc is in the free list. + /// When set, `prev` / `next` refer to other relocs in the free list. + /// All other fields are undefined. + free: bool, + _: u6 = 0, }, pub const Type = extern union { @@ -1169,6 +1175,10 @@ pub const Reloc = extern struct { none = std.math.maxInt(u32), _, + pub fn wrap(i: ?u32) Reloc.Index { + return @enumFromInt((i orelse return .none) + 1); + } + pub fn get(ri: Reloc.Index, coff: *Coff) *Reloc { return &coff.relocs.items[@intFromEnum(ri)]; } @@ -1490,7 +1500,24 @@ pub const Reloc = extern struct { .none => {}, else => |next| next.get(coff).prev = reloc.prev, } + reloc.* = undefined; + reloc.flags = .{ + .recover_addend = false, + .free = true, + }; + + const ri: Reloc.Index = .wrap(@intCast(reloc - coff.relocs.items.ptr)); + if (coff.last_free_reloc == .none) { + assert(coff.first_free_reloc == .none); + coff.first_free_reloc = ri; + coff.last_free_reloc = ri; + } else { + coff.last_free_reloc.get(coff).next = ri; + reloc.prev = coff.last_free_reloc; + reloc.next = .none; + coff.last_free_reloc = ri; + } } comptime { @@ -1630,6 +1657,8 @@ fn create( }), .pending_uavs = .empty, .relocs = .empty, + .first_free_reloc = .none, + .last_free_reloc = .none, .const_prog_node = .none, .synth_prog_node = .none, .symbol_prog_node = .none, @@ -3624,6 +3653,9 @@ const RelocAddend = union(enum) { pending: void, }; +// TODO: There should be an API where the caller can indicate how many contiguous relocs they need +// and it should attempt to allocate these from from the free list if available. We can cache +// the run length of each segment on Reloc when `free` is set. pub fn addReloc( coff: *Coff, loc_si: Symbol.Index, @@ -3747,6 +3779,7 @@ fn addRelocAssumeCapacity( .addend = if (addend == .pending) 0 else addend.known, .flags = .{ .recover_addend = addend == .pending, + .free = false, }, }; switch (target.target_relocs) { @@ -3756,17 +3789,6 @@ fn addRelocAssumeCapacity( target.target_relocs = ri; } -// pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { -// const diags = &coff.base.comp.link_diags; -// return coff.loadInputInner(input) catch |err| switch (err) { -// else => |e| return e, -// error.MappedFileIo => return diags.fail( -// "failed to write output file: {t}", -// .{coff.mf.io_err.?}, -// ), -// }; -// } - fn failLoadInput( coff: *Coff, err: LoadInputError, @@ -5673,6 +5695,7 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { var undef_indices: std.ArrayListUnmanaged(u32) = .empty; for (coff.relocs.items, 0..) |reloc, reloc_i| { + if (reloc.flags.free) continue; const target_sym = reloc.target.get(coff); switch (target_sym.ni) { .none => { @@ -7321,13 +7344,6 @@ fn updateExportsInner( const gpa = zcu.gpa; const ip = &zcu.intern_pool; - switch (exported) { - .nav => |nav| log.debug("updateExports({f})", .{ip.getNav(nav).fqn.fmt(ip)}), - .uav => |uav| log.debug("updateExports(@as({f}, {f}))", .{ - Type.fromInterned(ip.typeOf(uav)).fmt(pt), - Value.fromInterned(uav).fmtValue(pt), - }), - } try coff.symbols.ensureUnusedCapacity(gpa, export_indices.len); const exported_si: Symbol.Index = switch (exported) { .nav => |nav| try coff.navSymbol(zcu, nav), @@ -7337,6 +7353,14 @@ fn updateExportsInner( Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu), ))), }; + switch (exported) { + .nav => |nav| log.debug("updateExports({f}) = {d}", .{ ip.getNav(nav).fqn.fmt(ip), exported_si }), + .uav => |uav| log.debug("updateExports(@as({f}, {f})) = {d}", .{ + Type.fromInterned(ip.typeOf(uav)).fmt(pt), + Value.fromInterned(uav).fmtValue(pt), + exported_si, + }), + } while (try coff.resolve(pt.tid)) {} while (try coff.idle(pt.tid)) {} @@ -7349,7 +7373,7 @@ fn updateExportsInner( const @"export" = export_index.ptr(zcu); const name = @"export".opts.name.toSlice(ip); - // TODO: add an errMsg if this conflicts with an existing global + // TODO: add an errMsg if this conflicts with an existing symbol const export_si = try coff.globalSymbol(.{ .name = name, .lib_name = null, @@ -7360,7 +7384,7 @@ fn updateExportsInner( export_sym.section_number = exported_sym.section_number; if (@"export".opts.linkage == .weak and !coff.isImage()) { // exported_si needs to be ahead of export_si in the symbol table, - // so that its sti is known when creating the aux entry + // so that its sti is known when creating the weak external aux entry try coff.pendingSymbolTableEntry(exported_si); export_sym.flags.weak_external_strat = .alias; export_sym.setValue(.{ .weak_alias_si = exported_si }); @@ -7371,6 +7395,8 @@ fn updateExportsInner( const prev_alias_sym = prev_alias_si.get(coff); switch (prev_alias_sym.flags.extra_tag) { .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }), + // This export should have been deleted + .next_alias_si => assert(prev_alias_sym.extra.next_alias_si == export_si), else => unreachable, } @@ -7474,10 +7500,21 @@ fn updateExportsInner( } } -pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTerminatedString) void { - _ = coff; - _ = exported; - _ = name; +pub fn deleteExport( + coff: *Coff, + exported: Zcu.Exported, + name: InternPool.NullTerminatedString, +) void { + const zcu = coff.base.comp.zcu.?; + const ip = &zcu.intern_pool; + + const exported_si: Symbol.Index = switch (exported) { + .nav => |nav| coff.navs.get(nav).?, + .uav => |uav| coff.uavs.get(uav).?, + }; + + const name_slice = name.toSlice(ip); + log.debug("deleteExport({s}, {d})", .{ name_slice, exported_si }); // TODO: Delete from first / second linker member table // TODO: Delete from symbol table inside section -- 2.54.0 From f2a778ca523ebe5b84f0d5463be24ca43262eea3 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Thu, 11 Jun 2026 00:21:52 -0400 Subject: [PATCH 81/94] Coff: fixes for 32 bit targets --- lib/std/coff.zig | 2 +- src/link/Coff.zig | 41 ++++++++++++++++++++--------------------- src/link/MappedFile.zig | 2 +- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index ed33ef21bc3393a3d080fc95e23c8ee4a6063b85..d855495e399fff55db9a02ade2be45d0566a7095 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -2043,7 +2043,7 @@ pub const ArchiveMemberHeader = extern struct { if (opt_longnames) |longnames| { if (offset >= longnames.len) return error.BadName; - break :name std.mem.sliceTo(longnames[offset..], 0); + break :name std.mem.sliceTo(longnames[@intCast(offset)..], 0); } else return error.NoLongNames; } else if (trim[trim.len - 1] == '/') trim[0 .. trim.len - 1] diff --git a/src/link/Coff.zig b/src/link/Coff.zig index eba5d6222cdfad3507d488a5f0084f882f3131fa..c4a200727ffa9e6d116121edce6fd271edd023df 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -27,7 +27,7 @@ nodes: std.MultiArrayList(Node), members: std.ArrayList(Member), pending_members: std.AutoArrayHashMapUnmanaged(Member.Index, void), lib_string_table: std.ArrayList(String), -lib_string_len: u64, +lib_string_len: u32, long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, @@ -528,7 +528,7 @@ pub const Member = struct { try Node.known.longnames_member.resize(&coff.mf, gpa, new_size); const name_table_slice = Node.known.longnames_member.slice(&coff.mf); - const name_slice = name_table_slice[old_size..][0 .. name.len + 1]; + const name_slice = name_table_slice[@intCast(old_size)..][0 .. name.len + 1]; @memcpy(name_slice[0..name.len], name); name_slice[name.len] = 0; @@ -608,7 +608,7 @@ pub const LongNamesTable = struct { assert(adapter.coff.isArchive()); const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf); const rhs = adapter.coff.long_names_table.entries.values()[rhs_index]; - return std.mem.eql(u8, longnames_slice[rhs.offset..][0..rhs.len], lhs_key); + return std.mem.eql(u8, longnames_slice[@intCast(rhs.offset)..][0..@intCast(rhs.len)], lhs_key); } pub fn hash(_: Adapter, key: []const u8) u32 { @@ -2721,8 +2721,8 @@ fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !Symbo try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name.len + 1); const slice = coff.symbol_table.strings_ni.slice(&coff.mf); - @memcpy(slice[string_index..][0..name.len], name); - slice[string_index + name.len] = 0; + @memcpy(slice[@intCast(string_index)..][0..name.len], name); + slice[@intCast(string_index + name.len)] = 0; } break :name .{ .long = string_gop.value_ptr.* }; @@ -2941,7 +2941,7 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. } /// Caller guarantees there is capacity for one member and two nodes -fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: usize) !Member.Index { +fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: u64) !Member.Index { const comp = coff.base.comp; const gpa = comp.gpa; @@ -2987,7 +2987,7 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, const old_size = Node.known.second_linker_member.location(&coff.mf).resolve(&coff.mf)[1]; const old_header_size = new_num_members * @sizeOf(u32); - const trailing_size = old_size - old_header_size; + const trailing_size: usize = @intCast(old_size - old_header_size); try Node.known.second_linker_member.resize(&coff.mf, gpa, old_size + @sizeOf(u32)); const slice = Node.known.second_linker_member.slice(&coff.mf); @@ -3053,12 +3053,12 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void { // can't guarantee that they will be tightly packed after resizing const name_slice = name.toSlice(coff); - const new_string_table_size = coff.lib_string_len + name_slice.len + 1; + const new_string_table_size: u32 = @intCast(coff.lib_string_len + name_slice.len + 1); defer coff.lib_string_len = new_string_table_size; { - const old_header_size = @sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u32); - const new_header_size = old_header_size + @sizeOf(u32); + const old_header_size: usize = @intCast(@sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u32)); + const new_header_size: usize = @intCast(old_header_size + @sizeOf(u32)); try Node.known.first_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size); const slice = Node.known.first_linker_member.slice(&coff.mf); @@ -3231,7 +3231,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void { .unused = @splat(0), }; if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.SectionDefinition, .@"2", aux_ptr); + std.mem.byteSwapAllFieldsAligned(std.coff.WeakExternalDefinition, .@"2", aux_ptr); break :aux_init; } else switch (coff.getNode(sym.ni)) { @@ -3249,7 +3249,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void { .unused = @splat(0), }; if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.SectionDefinition, .@"2", aux_ptr); + std.mem.byteSwapAllFieldsAligned(std.coff.SectionDefinition, .@"2", aux_ptr); break :aux_init; }, @@ -4269,7 +4269,7 @@ fn loadObject( var weak_external: std.coff.WeakExternalDefinition = undefined; @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]); if (target_endian != native_endian) - std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external); + std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external); if (weak_external.tag_index >= header.number_of_symbols) return diags.failParse( @@ -5131,7 +5131,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) Loa symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, target_endian)) - 1; pos = fr.logicalPos(); - try coff.ensureManyUnusedStringCapacity(num_symbols, member_end - pos); + try coff.ensureManyUnusedStringCapacity(num_symbols, @intCast(member_end - pos)); try coff.input_archive_members.ensureUnusedCapacity(gpa, num_members); try coff.input_archive_symbols.ensureUnusedCapacity(gpa, num_symbols); try coff.input_archive_symbol_indices.ensureUnusedCapacity(gpa, num_symbols); @@ -5202,7 +5202,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) Loa .longnames => { // This member is optional if (std.mem.eql(u8, res.name, "//")) - opt_longnames = try r.readAlloc(gpa, res.size); + opt_longnames = try r.readAlloc(gpa, @intCast(res.size)); opt_expected_kind = null; break; @@ -5753,9 +5753,8 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { // TODO: Make this a helper for anything that needs to report "referenced by" notes 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)); + @enumFromInt(reloc.offset / @sizeOf(std.coff.ImageDataDirectory)); err.addNote("referenced by data directory entry: {t}", .{dir}); }, .optional_header => err.addNote("referenced by optional header field", .{}), @@ -6566,7 +6565,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const Entry = std.coff.ImportLookupTableEntry(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 = .{ + var import_hint_name_rvas: [2]Entry = .{ .{ .payload = if (import.name == .none) .{ .ordinal = .{ .ordinal = import.ordinal_hint } } @@ -6577,7 +6576,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)), }; if (native_endian != target_endian) - for (import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v); + for (&import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v); import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas; import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas; @@ -7182,7 +7181,7 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void { .strings = coff.lib_string_table.items, }); - var offset: u64 = 0; + var offset: usize = 0; var string_table = coff.secondLinkerMemberStringsSlice(); for (coff.lib_string_table.items) |string| { const str = string.toSlice(coff); @@ -7419,7 +7418,7 @@ fn updateExportsInner( if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); - const name_index: u64 = coff.export_table.name_table_ni.location(&coff.mf).resolve(&coff.mf)[1]; + const name_index: u32 = @intCast(coff.export_table.name_table_ni.location(&coff.mf).resolve(&coff.mf)[1]); const new_name_table_size = name_index + name.len + 1; if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 5c2892ecaa245f62974b132e979a3c68940bfa56..63a4fc78b335b8ee9f01dadd3dc2ae6e48d8ff2e 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1064,7 +1064,7 @@ fn realignNode( }; if (try_backward) { - const backward_offset = new_alignment.backward(old_offset); + const backward_offset = new_alignment.backward(@intCast(old_offset)); const prev_end = if (node.prev == .none) 0 else prev: { const prev_offset, const prev_size = node.prev.location(mf).resolve(mf); break :prev prev_offset + prev_size; -- 2.54.0 From 0405f7881828b2ba251fc7fe5724d91f9a658c16 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Thu, 11 Jun 2026 02:18:49 -0400 Subject: [PATCH 82/94] Coff: fixup ArrayHashMapUnmanaged usage --- src/link/Coff.zig | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index c4a200727ffa9e6d116121edce6fd271edd023df..ecfdab5242fff34bf03ce571b537c56f1c96817b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -25,24 +25,24 @@ base: link.File, mf: MappedFile, nodes: std.MultiArrayList(Node), members: std.ArrayList(Member), -pending_members: std.AutoArrayHashMapUnmanaged(Member.Index, void), +pending_members: std.array_hash_map.Auto(Member.Index, void), lib_string_table: std.ArrayList(String), lib_string_len: u32, long_names_table: LongNamesTable, import_table: ImportTable, export_table: ExportTable, symbol_table: SymbolTable, -inputs: std.ArrayHashMapUnmanaged(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false), +inputs: std.array_hash_map.Custom(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false), input_archives: std.ArrayList(InputArchive), input_archive_members: std.ArrayList(InputArchive.Member), input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol), -input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, InputArchive.SearchList), +input_archive_symbol_indices: std.array_hash_map.Auto(String, InputArchive.SearchList), pending_input: ?InputArchive.Member.Index, pending_default_libs: std.ArrayList(struct { path: []const u8, ioi: InputObject.Index, }), -alternate_names: std.AutoArrayHashMapUnmanaged(String, String), +alternate_names: std.array_hash_map.Auto(String, String), input_objects: std.ArrayList(InputObject), input_symbols: std.ArrayList(struct { si: Symbol.Index, name: String }), input_sections: std.ArrayList(Node.InputSection), @@ -57,10 +57,10 @@ strings: std.HashMapUnmanaged( std.hash_map.default_max_load_percentage, ), string_bytes: std.ArrayList(u8), -section_table: std.AutoArrayHashMapUnmanaged(String, Section), +section_table: std.array_hash_map.Auto(String, Section), pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index), object_section_table: std.array_hash_map.Auto(String, Symbol.Index), -section_merges: std.AutoArrayHashMapUnmanaged(String, String), +section_merges: std.array_hash_map.Auto(String, String), section_merge_pending_index: u32, symbols: std.ArrayList(Symbol), globals: std.array_hash_map.Auto(GlobalName, Symbol.Index), @@ -480,7 +480,7 @@ pub const Member = struct { kind: std.coff.ArchiveMemberHeader.Kind, header_ni: MappedFile.Node.Index, content_ni: MappedFile.Node.Index, - first_linker_indices: std.AutoArrayHashMapUnmanaged(struct { + first_linker_indices: std.array_hash_map.Auto(struct { mi: Member.Index, name: String, }, FirstLinkerIndex), @@ -594,7 +594,7 @@ pub const Member = struct { pub const LongNamesTable = struct { ni: MappedFile.Node.Index = .none, - entries: std.AutoArrayHashMapUnmanaged(void, Entry), + entries: std.array_hash_map.Auto(void, Entry), pub const Entry = struct { offset: u64, @@ -621,8 +621,8 @@ pub const LongNamesTable = struct { pub const SymbolTable = struct { ni: MappedFile.Node.Index, strings_ni: MappedFile.Node.Index, - strings: std.AutoArrayHashMapUnmanaged(String, StringIndex), - symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, SymbolTable.Index), + strings: std.array_hash_map.Auto(String, StringIndex), + symbols: std.array_hash_map.Auto(Symbol.Index, SymbolTable.Index), pending_symbol_index: u32, // Resizing the symbol table node has the result of accumulating padding @@ -681,7 +681,7 @@ pub const ExportTable = struct { name_pointer_table_ni: MappedFile.Node.Index, ordinal_table_ni: MappedFile.Node.Index, name_table_ni: MappedFile.Node.Index, - entries: std.AutoArrayHashMapUnmanaged(void, Entry), + entries: std.array_hash_map.Auto(void, Entry), pending_sort: bool = false, pub const Entry = struct { @@ -719,7 +719,7 @@ pub const ExportTable = struct { pub const ImportTable = struct { ni: MappedFile.Node.Index, entries: std.array_hash_map.Auto(void, Entry), - iat_symbol_indices: std.AutoArrayHashMapUnmanaged(struct { + iat_symbol_indices: std.array_hash_map.Auto(struct { iti: ImportTable.Index, name: String.Optional, // If name == .none this is the ordinal, otherwise the hint @@ -2767,7 +2767,7 @@ const GlobalOptions = struct { fn getOrPutGlobalSymbol( coff: *Coff, opts: GlobalOptions, -) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult { +) !std.array_hash_map.Auto(GlobalName, Symbol.Index).GetOrPutResult { const comp = coff.base.comp; const gpa = comp.gpa; try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -4134,7 +4134,7 @@ fn loadObject( }; var num_global_symbols: u32 = 0; - var pending_symbols: std.AutoArrayHashMapUnmanaged(u32, PendingSymbol) = .empty; + var pending_symbols: std.array_hash_map.Auto(u32, PendingSymbol) = .empty; defer pending_symbols.deinit(gpa); if (!is_archive) try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); -- 2.54.0 From 29f3d1185cd39daa6b0cbe70ecb05543dbeb6394 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Thu, 11 Jun 2026 02:19:24 -0400 Subject: [PATCH 83/94] resinator: fixup symbol type --- lib/compiler/resinator/cvtres.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/resinator/cvtres.zig b/lib/compiler/resinator/cvtres.zig index 244ab3f63596bfe01365761e1a5e2db084f48a2e..52f3a70ca0b4162ae5fc2248c29205b686ce56bb 100644 --- a/lib/compiler/resinator/cvtres.zig +++ b/lib/compiler/resinator/cvtres.zig @@ -383,7 +383,7 @@ pub fn writeCoff( fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void { try writer.writeAll(&symbol.name); try writer.writeInt(u32, symbol.value, .little); - try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little); + try writer.writeInt(i16, @intFromEnum(symbol.section_number), .little); try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little); try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little); try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little); -- 2.54.0 From 7fa719ef077afdc1038cd03e6f52a0e64adf221a Mon Sep 17 00:00:00 2001 From: kcbanner Date: Thu, 11 Jun 2026 02:22:44 -0400 Subject: [PATCH 84/94] test/standalone/shared_library: fix incorrect no_llvm check, skip on aarch64 --- test/standalone/shared_library/build.zig | 17 +++++++++-------- test/standalone/static_c_lib/build.zig | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index a7a55c28a20b97f8493a964d468fe4477d82c444..20b9df685f8b58798eec82a8d2559b07689e66e4 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -38,14 +38,15 @@ pub fn build(b: *std.Build) void { lib_use_llvm, exe_use_llvm, ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| { - const use_llvm = lib_llvm or exe_llvm; - if (!use_llvm and target.result.os.tag == .macos) continue; // TODO - if (!use_llvm and target.result.os.tag == .freebsd) continue; // TODO - if (!use_llvm and target.result.os.tag == .netbsd) continue; // TODO - if (!use_llvm and target.result.os.tag == .openbsd) continue; // TODO - if (!use_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO - if (!use_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO - if (!use_llvm and target.result.cpu.arch == .s390x) continue; // TODO + const no_llvm = !lib_llvm or !exe_llvm; + if (no_llvm and target.result.os.tag == .macos) continue; // TODO + if (no_llvm and target.result.os.tag == .freebsd) continue; // TODO + if (no_llvm and target.result.os.tag == .netbsd) continue; // TODO + if (no_llvm and target.result.os.tag == .openbsd) continue; // TODO + if (no_llvm and target.result.cpu.arch == .aarch64) continue; // TODO + if (no_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO + if (no_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO + if (no_llvm and target.result.cpu.arch == .s390x) continue; // TODO const lib = b.addLibrary(.{ .linkage = .dynamic, diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig index a1e5c034fe10e4ebdd918264185e410964db9e73..5b35871dceca1a00abbac632adab8f0edc4add69 100644 --- a/test/standalone/static_c_lib/build.zig +++ b/test/standalone/static_c_lib/build.zig @@ -38,14 +38,15 @@ pub fn build(b: *std.Build) void { lib_use_llvm, exe_use_llvm, ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| { - const use_llvm = lib_llvm or exe_llvm; - if (!use_llvm and target.result.os.tag == .macos) continue; // TODO - if (!use_llvm and target.result.os.tag == .freebsd) continue; // TODO - if (!use_llvm and target.result.os.tag == .netbsd) continue; // TODO - if (!use_llvm and target.result.os.tag == .openbsd) continue; // TODO - if (!use_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO - if (!use_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO - if (!use_llvm and target.result.cpu.arch == .s390x) continue; // TODO + const no_llvm = !lib_llvm or !exe_llvm; + if (no_llvm and target.result.os.tag == .macos) continue; // TODO + if (no_llvm and target.result.os.tag == .freebsd) continue; // TODO + if (no_llvm and target.result.os.tag == .netbsd) continue; // TODO + if (no_llvm and target.result.os.tag == .openbsd) continue; // TODO + if (no_llvm and target.result.cpu.arch == .aarch64) continue; // TODO + if (no_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO + if (no_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO + if (no_llvm and target.result.cpu.arch == .s390x) continue; // TODO const foo = b.addLibrary(.{ .linkage = .static, -- 2.54.0 From 09b51b3dc1a5d6ea73637513efd39be313f8301e Mon Sep 17 00:00:00 2001 From: kcbanner Date: Thu, 11 Jun 2026 23:48:12 -0400 Subject: [PATCH 85/94] objdump: fixup incorrect type in byteSwap --- lib/compiler/objdump.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 56746f712897835e33531511b5ad456d860c312e..539a4fe384036a1de025fa3c1585181f869dcbe0 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -1029,7 +1029,7 @@ const coff = struct { var weak_external: std.coff.WeakExternalDefinition = undefined; @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]); if (native_endian != .little) - std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external); + std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external); if (weak_external.tag_index >= header.number_of_symbols) return d.failParse( -- 2.54.0 From b981b7975dad40e4e779f848530bf80e54b7cde4 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 13 Jun 2026 12:55:01 -0400 Subject: [PATCH 86/94] Coff: fixup overflow in relocation bounds check --- src/link/Coff.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ecfdab5242fff34bf03ce571b537c56f1c96817b..18a69c0ad20e4d75b136f89580dfed26ddc89343 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -4055,14 +4055,14 @@ fn loadObject( section.name = coff.getOrPutStringAssumeCapacity(section_name_slice); if (section.header.pointer_to_linenumbers + - section.header.number_of_linenumbers * std.coff.LineNumber.sizeOf() > fl.size) + @as(u32, section.header.number_of_linenumbers) * std.coff.LineNumber.sizeOf() > fl.size) return diags.failParse(path, "bad line numbers location in section {d} `{s}`", .{ section_i, section_name_slice, }); if (section.header.pointer_to_relocations + - section.header.number_of_relocations * std.coff.Relocation.sizeOf() > fl.size) + @as(u32, section.header.number_of_relocations) * std.coff.Relocation.sizeOf() > fl.size) return diags.failParse(path, "bad relocations location in section {d} `{s}`", .{ section_i, section_name_slice, -- 2.54.0 From e2c587ae54cba1d2b13b5cbef8530e13fab04c7f Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 13 Jun 2026 19:01:00 -0400 Subject: [PATCH 87/94] objdump: when redacting ordinals, show weak external alias as relative to the WEAK_EXTERNAL symbol objdump: dump the filename for file aux symbols test/link: remove the text attribute on the snapshots folder to avoid changing snapshot line endings --- lib/compiler/objdump.zig | 22 +++++++++++++--------- test/link/snapshots/.gitattributes | 1 + test/link/snapshots/static-lib.llvm.dmp | 2 +- test/link/snapshots/static-lib.no-llvm.dmp | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 test/link/snapshots/.gitattributes diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index 539a4fe384036a1de025fa3c1585181f869dcbe0..c3f6f5e8a7a444862b1396427fcf589805725942 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -1037,21 +1037,25 @@ const coff = struct { .{ weak_external.tag_index, symbol_i }, ); - try w.print(" Weak External [falls back to {x:0>8} via {t}]", .{ - weak_external.tag_index, - weak_external.flag, - }); + if (d.redacted(.ord)) + try w.print(" Weak External [falls back to relative ordinal {x:0>8} via {t}]", .{ + @as(i64, weak_external.tag_index) - symbol_i, + weak_external.flag, + }) + else + try w.print(" Weak External [falls back to ordinal {x:0>8} via {t}]", .{ + weak_external.tag_index, + weak_external.flag, + }); } else if (symbol.storage_class == .FILE) { if (!std.mem.eql(u8, name, ".file")) { try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i }); continue; } - var file: std.coff.FileDefinition = undefined; - @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]); - - // TODO - _ = file.getFileName(); + const filename = std.mem.sliceTo(aux_symbols, 0); + try w.print(" File '{s}'", .{filename}); + break; } else if (symbol.storage_class == .STATIC and symbol.type == std.coff.SymType{ .complex_type = .NULL, diff --git a/test/link/snapshots/.gitattributes b/test/link/snapshots/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..625449502ba4b4d7c229c6ccee42e3ec41cc780c --- /dev/null +++ b/test/link/snapshots/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/test/link/snapshots/static-lib.llvm.dmp b/test/link/snapshots/static-lib.llvm.dmp index 0b4f7ab2e207befefe8401b67213b0c8e74ab3ed..f64e76be1134c735e77bb6e2e10d0983a8858d35 100644 --- a/test/link/snapshots/static-lib.llvm.dmp +++ b/test/link/snapshots/static-lib.llvm.dmp @@ -11,5 +11,5 @@ xxxx 00000000 2 NULL EXTERNAL | foo_strong xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias xxxx 00000008 2 NULL EXTERNAL | foo_array xxxx 00000000 UNDEF NULL WEAK_EXTERNAL | fooWeak - | Weak External [falls back to 00000025 via SEARCH_ALIAS] + | Weak External [falls back to relative ordinal 000000+2 via SEARCH_ALIAS] xxxx 00000000 1 NULL() EXTERNAL | .weak.fooWeak.default.foo_strong diff --git a/test/link/snapshots/static-lib.no-llvm.dmp b/test/link/snapshots/static-lib.no-llvm.dmp index 0c9a4c1e8254cf6a162a91cef2be6799b7a4ba59..07e2219bfd57889557bd5eda680dc5de4ba62e42 100644 --- a/test/link/snapshots/static-lib.no-llvm.dmp +++ b/test/link/snapshots/static-lib.no-llvm.dmp @@ -9,4 +9,4 @@ xxxx 00000000 2 NULL EXTERNAL | foo_strong xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias xxxx 00000010 2 NULL EXTERNAL | foo_array xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak - | Weak External [falls back to 0000000d via SEARCH_ALIAS] + | Weak External [falls back to relative ordinal 000000-4 via SEARCH_ALIAS] -- 2.54.0 From c9601664c6914a8bdbf778f494a0a4fe6d84fa53 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 13 Jun 2026 23:40:30 -0400 Subject: [PATCH 88/94] Coff: fix use of stale header pointer (due to node resize) in addRelocAssumeCapacity MappedFile: add check to prevent INVAL from fallocate() when realigning nodes that are not aligned to the block size --- src/link/Coff.zig | 54 ++++++++++++++++++++++------------------- src/link/MappedFile.zig | 25 ++++++++++++------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 18a69c0ad20e4d75b136f89580dfed26ddc89343..58cf6f11947702e85c5d31db6fa79c5e61d865c2 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -3730,33 +3730,37 @@ fn addRelocAssumeCapacity( break :sti .none; } else .none; - const section = loc_sn.section(coff); - const header = loc_sn.header(coff); - const old_num_relocations = coff.targetLoad(&header.number_of_relocations); - const new_num_relocations = old_num_relocations + 1; - const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); - if (section.relocation_table_ni == .none) { - section.relocation_table_ni = try coff.mf.addLastChildNode( - gpa, - coff.sectionParent(), - .{ - .size = new_size, - .alignment = .@"2", - .moved = true, - .resized = true, - }, - ); - coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn }); - } else { - try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); - } + const sri: Section.RelocationIndex = blk: { + const section = loc_sn.section(coff); + const header = loc_sn.header(coff); + const old_num_relocations = coff.targetLoad(&header.number_of_relocations); + const new_num_relocations = old_num_relocations + 1; + const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); - coff.targetStore(&header.number_of_relocations, new_num_relocations); - if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr| - coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations); + coff.targetStore(&header.number_of_relocations, new_num_relocations); + if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr| + coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations); + + if (section.relocation_table_ni == .none) { + section.relocation_table_ni = try coff.mf.addLastChildNode( + gpa, + coff.sectionParent(), + .{ + .size = new_size, + .alignment = .@"2", + .moved = true, + .resized = true, + }, + ); + coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn }); + } else { + try section.relocation_table_ni.resize(&coff.mf, gpa, new_size); + } + + // TODO: These need to allocate from a free list, once deleting relocs from the table is supported + break :blk .wrap(old_num_relocations); + }; - // TODO: These need to allocate from a free list, once deleting relocs is supported - const sri: Section.RelocationIndex = .wrap(old_num_relocations); const entry = sri.entry(coff, loc_sn).?; if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 63a4fc78b335b8ee9f01dadd3dc2ae6e48d8ff2e..669274d055e2fd019722a0ff1ea075490a8033df 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -799,19 +799,26 @@ fn resizeNode( if (is_linux and !mf.flags.fallocate_insert_range_unsupported and node.flags.alignment.order(mf.flags.block_size).compare(.gte)) insert_range: { - mf.memory_map.write(io) catch |err| switch (err) { - error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking - error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing - else => |e| return e, - }; - // Ask the filesystem driver to insert extents into the file without copying any data - const last_offset, const last_size = parent.last.location(mf).resolve(mf); - const last_end = last_offset + last_size; - assert(last_end <= old_parent_size); const range_file_offset = ni.fileLocation(mf, false).offset + old_size; const range_size = node.flags.alignment.forward( @intCast(requested_size +| requested_size / growth_factor), ) - old_size; + + // If this node is being realigned, its current state might not + // meet the requirements for fallocate + if (!mf.flags.block_size.check(@intCast(range_file_offset)) or + !mf.flags.block_size.check(@intCast(range_size))) + break :insert_range; + + mf.memory_map.write(io) catch |err| switch (err) { + error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking + error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing + else => |e| return e, + }; + // Ask the filesystem driver to insert extents into the file without copying any data + const last_offset, const last_size = parent.last.location(mf).resolve(mf); + const last_end = last_offset + last_size; + assert(last_end <= old_parent_size); _, const file_size = Node.Index.root.location(mf).resolve(mf); while (true) switch (linux.errno(switch (std.math.order(range_file_offset, file_size)) { .lt => linux.fallocate( -- 2.54.0 From 411e5099e5fb8a8e1b39e0bcd0098e7a2c162aec Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 16 Jun 2026 00:22:17 -0400 Subject: [PATCH 89/94] link: remove debug_link_snapshots in favour of enable_debug_extensions MappedFile: use opts struct for realign Coff: fixup not reserving enough string capacity in loadObject --- build.zig | 3 --- src/crash_report.zig | 13 +++++-------- src/link.zig | 7 ++++--- src/link/Coff.zig | 16 +++++++++------- src/link/Elf2.zig | 10 ++++------ src/link/MappedFile.zig | 25 ++++++++++++++++--------- src/main.zig | 4 ++-- 7 files changed, 40 insertions(+), 38 deletions(-) diff --git a/build.zig b/build.zig index d2585639bcad8f272cd7ef95abd9e56dc759784f..16cc22514d34cb9de9536465fa1c142665234f9e 100644 --- a/build.zig +++ b/build.zig @@ -256,7 +256,6 @@ pub fn build(b: *std.Build) !void { const is_debug = optimize == .Debug; const enable_debug_extensions = b.option(bool, "debug-extensions", "Enable commands and options useful for debugging the compiler") orelse is_debug; const enable_logging = b.option(bool, "log", "Enable debug logging with --debug-log") orelse is_debug; - const enable_link_snapshots = b.option(bool, "link-snapshot", "Whether to enable linker state snapshots") orelse false; const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git."); const version_slice = if (opt_version_string) |version| version else v: { @@ -372,7 +371,6 @@ pub fn build(b: *std.Build) !void { exe_options.addOption(bool, "enable_debug_extensions", enable_debug_extensions); exe_options.addOption(bool, "enable_logging", enable_logging); - exe_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots); exe_options.addOption(bool, "enable_tracy", tracy != null); exe_options.addOption(bool, "enable_tracy_callstack", tracy_callstack); exe_options.addOption(bool, "enable_tracy_allocation", tracy_allocation); @@ -733,7 +731,6 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { exe_options.addOption(std.SemanticVersion, "semver", semver); exe_options.addOption(bool, "enable_debug_extensions", false); exe_options.addOption(bool, "enable_logging", false); - exe_options.addOption(bool, "enable_link_snapshots", false); exe_options.addOption(bool, "enable_tracy", false); exe_options.addOption(bool, "enable_tracy_callstack", false); exe_options.addOption(bool, "enable_tracy_allocation", false); diff --git a/src/crash_report.zig b/src/crash_report.zig index 4294527c4511fa756af417a0c4b12e24701d421d..63370d0557d6799fe584794a84026963077b7a71 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -132,14 +132,11 @@ fn dumpCrashContext() Io.Writer.Error!void { try dumpCrashContextSema(anal, w, &S.crash_heap); } else if (LinkerOp.current) |linker_op| { try w.writeAll("Linker snapshot:\n"); - if (build_options.enable_link_snapshots) { - 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 => {}, - } - } else { - try w.writeAll("(build with -Dlink-snapshot to dump linker state)"); + switch (try linker_op.lf.dump(w, linker_op.tid)) { + .unimplemented => try w.writeAll("(backend does not support link snapshots)"), + .needs_extensions => try w.writeAll("(build with -Ddebug-extensions to dump linker state)"), + .disabled => try w.writeAll("(run with --debug-link-snapshot to dump linker state)"), + .enabled => {}, } try w.writeAll("\n\n"); } else { diff --git a/src/link.zig b/src/link.zig index 6352c03b0282e8ebadf26234dce2228673e42195..688812dd0ecb140fbefa0da5ea4fd4acd70f895d 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1091,13 +1091,14 @@ pub const File = struct { } pub const DumpResult = enum { - unsupported, + unimplemented, + needs_extensions, disabled, enabled, }; pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !DumpResult { - if (!build_options.enable_link_snapshots) unreachable; + if (!build_options.enable_debug_extensions) return .not_built; switch (base.tag) { .elf, .macho, @@ -1106,7 +1107,7 @@ pub const File = struct { .spirv, .plan9, .lld, - => return .unsupported, + => return .unimplemented, 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 index 58cf6f11947702e85c5d31db6fa79c5e61d865c2..006f99d551e5b02c9afc3aa474e08ecec3572eb7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -22,6 +22,7 @@ const implib = @import("../libs/mingw/implib.zig"); const Path = std.Build.Cache.Path; base: link.File, +options: link.File.OpenOptions, mf: MappedFile, nodes: std.MultiArrayList(Node), members: std.ArrayList(Member), @@ -82,7 +83,6 @@ synth_prog_node: std.Progress.Node, symbol_prog_node: std.Progress.Node, member_prog_node: std.Progress.Node, input_prog_node: std.Progress.Node, -dump_snapshot: bool, pub const default_file_alignment: u16 = 0x200; pub const default_size_of_stack_reserve: u32 = 0x1000000; @@ -1593,6 +1593,7 @@ fn create( .allow_shlib_undefined = false, .stack_size = 0, }, + .options = options, .mf = try .init(file, comp.gpa, io), .nodes = .empty, .members = .empty, @@ -1664,7 +1665,6 @@ fn create( .symbol_prog_node = .none, .member_prog_node = .none, .input_prog_node = .none, - .dump_snapshot = options.enable_link_snapshots, }; errdefer coff.deinit(); @@ -3579,13 +3579,13 @@ fn objectSectionMapIndex( const parent_alignment = parent_ni.alignment(&coff.mf); if (alignment.compare(.gt, parent_alignment)) { log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment }); - try parent_ni.realign(&coff.mf, gpa, alignment, true); + try parent_ni.realign(&coff.mf, gpa, alignment, .{ .set_alignment = true }); } const old_alignment = sym.ni.alignment(&coff.mf); if (alignment.compare(.gt, old_alignment)) { log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment }); - try sym.ni.realign(&coff.mf, gpa, alignment, true); + try sym.ni.realign(&coff.mf, gpa, alignment, .{ .set_alignment = true }); } try coff.verifyParentSectionAttributes( @@ -3980,7 +3980,9 @@ fn loadObject( try coff.ensureManyUnusedStringCapacity( header.number_of_sections + header.number_of_symbols, - string_table_len - @sizeOf(u32), + header.number_of_sections * 9 + + header.number_of_symbols * 9 + + string_table_len - @sizeOf(u32), ); const PendingSymbolIndex = enum(u32) { @@ -5886,7 +5888,7 @@ pub fn flush( else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), }; - if (coff.dump_snapshot) + if (coff.options.enable_link_snapshots) coff.dumpStderr(tid) catch |err| return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err}); } @@ -7534,7 +7536,7 @@ 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) { + if (coff.options.enable_link_snapshots) { try coff.printNode(tid, w, .root, 0); try w.writeAll("Section table:\n"); for (coff.section_table.keys(), coff.section_table.values()) |name, sec| diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index dbed210d8b3c0ae5c24bfa631fa0669fbebb485f..8210a4f3c0e1f163a62fb60550946b14040cbccd 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -161,7 +161,6 @@ 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}; @@ -2625,7 +2624,6 @@ fn create( .synth_prog_node = .none, .input_prog_node = .none, .textrel_count = 0, - .dump_snapshot = options.enable_link_snapshots, }; errdefer elf.deinit(); @@ -3787,7 +3785,7 @@ fn mapInputSection(elf: *Elf, opts: struct { const new_alignment: std.mem.Alignment = .fromByteUnits( std.math.ceilPowerOfTwoAssert(usize, @intCast(opts.addralign)), ); - try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment, true); + try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment, .{ .set_alignment = true }); } // ...and update the shdr as needed. switch (elf.shdrPtr(existing_shndx)) { @@ -3950,7 +3948,7 @@ fn uavMapIndex( } else { const node = uav_gop.value_ptr.lsi.index().ptr(elf).node; if (resolved_align.toStdMem().order(node.alignment(&elf.mf)).compare(.gt)) { - try node.realign(&elf.mf, gpa, resolved_align.toStdMem(), true); + try node.realign(&elf.mf, gpa, resolved_align.toStdMem(), .{ .set_alignment = true }); } } return umi; @@ -4679,7 +4677,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars // We have a copy relocation for this global, but the amount of space we // reserved for it could be too small or underaligned! try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size); - try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, true); + try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, .{ .set_alignment = true }); const global_ptr = elf.globalByName(name).?; switch (elf.symPtr(global_ptr.symtab_index)) { inline else => |sym_ptr| elf.targetStore(&sym_ptr.size, @intCast(gop.value_ptr.size)), @@ -6714,7 +6712,7 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm } pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult { - if (elf.dump_snapshot) { + if (elf.options.enable_link_snapshots) { try elf.printNode(tid, w, .root, 0); return .enabled; } diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 669274d055e2fd019722a0ff1ea075490a8033df..03b92315aa32ebf8a9baa8a4960354292a5d4634 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -354,18 +354,23 @@ pub const Node = extern struct { } } + pub const RealignNodeOptions = struct { + /// Shift the node backwards if possible + try_backwards: bool = true, + /// If `set, persists `new_alignment` as the node's alignment for future operations. + set_alignment: bool = true, + }; + /// Moves and expands a node such that its offset and size are aligned to `new_alignment`. - /// If it is possible to move the node backwards, this will be done instead of moving it forward. - /// If `set_alignment` is set, persists `new_alignment` as the node's alignment for future operations. /// Asserts that `ni` is not `Node.Index.root`. pub fn realign( ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, new_alignment: std.mem.Alignment, - set_alignment: bool, + opts: RealignNodeOptions, ) Error!void { - mf.realignNode(gpa, ni, new_alignment, true, set_alignment) catch |err| switch (err) { + mf.realignNode(gpa, ni, new_alignment, opts) catch |err| switch (err) { error.OutOfMemory, error.Canceled, => |e| return e, @@ -573,7 +578,10 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct { else => |next_ni| { const next_offset, _ = next_ni.location(mf).resolve(mf); if (new_end > next_offset) - try next_ni.realign(mf, gpa, opts.add_node.alignment, false); + try next_ni.realign(mf, gpa, opts.add_node.alignment, .{ + .try_backwards = false, + .set_alignment = false, + }); }, } } @@ -1035,8 +1043,7 @@ fn realignNode( gpa: std.mem.Allocator, ni: Node.Index, new_alignment: std.mem.Alignment, - try_backward: bool, - set_alignment: bool, + opts: Node.Index.RealignNodeOptions, ) (Allocator.Error || Io.Cancelable || IoError)!void { assert(ni != Node.Index.root); // currently unsupported mf.nodes_lock.assertUnlocked(); @@ -1052,7 +1059,7 @@ fn realignNode( node.flags.alignment = new_alignment; defer { // alignment needs to be temporarily set for the resizes below - if (!set_alignment) node.flags.alignment = prev_alignment; + if (!opts.set_alignment) node.flags.alignment = prev_alignment; } const new_size = node.flags.alignment.forward(@intCast(size)); @@ -1070,7 +1077,7 @@ fn realignNode( }, }; - if (try_backward) { + if (opts.try_backwards) { const backward_offset = new_alignment.backward(@intCast(old_offset)); const prev_end = if (node.prev == .none) 0 else prev: { const prev_offset, const prev_size = node.prev.location(mf).resolve(mf); diff --git a/src/main.zig b/src/main.zig index 1c33a168f4ef7e0dae3608885f994eb49fe4cd82..4dd56dd3b0623c7836ce04604c9066b562a223a4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1440,8 +1440,8 @@ fn buildOutputType( dev.check(.stdio_listen); listen = .stdio; } else if (mem.eql(u8, arg, "--debug-link-snapshot")) { - if (!build_options.enable_link_snapshots) { - warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{}); + if (!build_options.enable_debug_extensions) { + warn("Zig was compiled without debug extensions. --debug-link-snapshot has no effect.", .{}); } else { enable_link_snapshots = true; } -- 2.54.0 From 323a3edafe0ac3e3b101db87188fe9ace1d9c388 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 23 Jun 2026 00:19:44 -0400 Subject: [PATCH 90/94] Coff: Rework global keys The previous way of keying globals on (name, lib_name) did not allow resolving undef externals from imports to globals that were first seen with a lib_name --- src/link/Coff.zig | 212 ++++++++++++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 93 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 006f99d551e5b02c9afc3aa474e08ecec3572eb7..5ef4a76e228f04b7434993a7f148081d81151645 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -64,7 +64,7 @@ object_section_table: std.array_hash_map.Auto(String, Symbol.Index), section_merges: std.array_hash_map.Auto(String, String), section_merge_pending_index: u32, symbols: std.ArrayList(Symbol), -globals: std.array_hash_map.Auto(GlobalName, Symbol.Index), +globals: std.array_hash_map.Auto(String, Global), global_pending_index: u32, navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index), uavs: std.array_hash_map.Auto(InternPool.Index, Symbol.Index), @@ -268,12 +268,16 @@ pub const Node = union(enum) { }; } - pub fn globalName(gmi: GlobalMapIndex, coff: *const Coff) GlobalName { + pub fn name(gmi: GlobalMapIndex, coff: *const Coff) String { return coff.globals.keys()[gmi.unwrap().?]; } pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index { - return coff.globals.values()[gmi.unwrap().?]; + return coff.globals.values()[gmi.unwrap().?].si; + } + + pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) String.Optional { + return coff.globals.values()[gmi.unwrap().?].lib_name; } }; @@ -335,6 +339,10 @@ pub const Node = union(enum) { const LocalIndex = enum(u32) { _, + + pub fn name(isli: LocalIndex, coff: *const Coff) String { + return coff.input_symbols.items[@intFromEnum(isli)].name; + } }; }; @@ -847,12 +855,15 @@ pub const Section = struct { ) ?*align(2) std.coff.Relocation { if (sri == .none) return null; const table_slice = sn.section(coff).relocation_table_ni.slice(&coff.mf); - return @ptrCast(@alignCast(&table_slice[sri.unwrap().? * std.coff.Relocation.sizeOf()])); + return @ptrCast(@alignCast(&table_slice[@as(u32, sri.unwrap().?) * std.coff.Relocation.sizeOf()])); } }; }; -pub const GlobalName = struct { name: String, lib_name: String.Optional }; +pub const Global = struct { + si: Symbol.Index, + lib_name: String.Optional, +}; pub const WeakExternalStrat = enum(u3) { none, @@ -1332,7 +1343,7 @@ pub const Reloc = extern struct { // so that this function doesn't return an err else => |kind| return coff.base.comp.link_diags.fail( "absolute symbol '{s}' targeted by invalid relocation type: {t}", - .{ target_sym.gmi.globalName(coff).name.toSlice(coff), kind }, + .{ target_sym.gmi.name(coff).toSlice(coff), kind }, ), .ABSOLUTE => {}, .ADDR64 => std.mem.writeInt( @@ -1351,7 +1362,7 @@ pub const Reloc = extern struct { .I386 => switch (reloc.type.I386) { else => |kind| return coff.base.comp.link_diags.fail( "absolute symbol '{s}' targeted by invalid relocation type: {t}", - .{ target_sym.gmi.globalName(coff).name.toSlice(coff), kind }, + .{ target_sym.gmi.name(coff).toSlice(coff), kind }, ), .ABSOLUTE => {}, .DIR16 => std.mem.writeInt( @@ -2767,12 +2778,12 @@ const GlobalOptions = struct { fn getOrPutGlobalSymbol( coff: *Coff, opts: GlobalOptions, -) !std.array_hash_map.Auto(GlobalName, Symbol.Index).GetOrPutResult { +) !std.array_hash_map.Auto(String, Global).GetOrPutResult { const comp = coff.base.comp; const gpa = comp.gpa; try coff.symbols.ensureUnusedCapacity(gpa, 1); - const lib_name = if (opts.lib_name) |lib_name| lib_name: { + const lib_name: String.Optional = if (opts.lib_name) |lib_name| lib_name: { const is_libc = std.zig.target.isLibCLibName(&comp.root_mod.resolved_target.result, lib_name); if (is_libc) { // This is guaranteed by Sema.handleExternLibName @@ -2781,23 +2792,23 @@ fn getOrPutGlobalSymbol( // TODO: The user has requested this symbol come from libc, but this logic allows // it to come from anywhere. We need to know what inputs are libc inputs, // and set a flag to only search them for this symbol. - break :lib_name null; + break :lib_name .none; } - break :lib_name lib_name; - } else null; + break :lib_name (try coff.getOrPutString(lib_name)).toOptional(); + } else .none; - const sym_gop = try coff.globals.getOrPut(gpa, .{ - .name = try coff.getOrPutString(opts.name), - .lib_name = try coff.getOrPutOptionalString(lib_name), - }); + const sym_gop = try coff.globals.getOrPut(gpa, try coff.getOrPutString(opts.name)); if (!sym_gop.found_existing) { const si = coff.addSymbolAssumeCapacity(); const sym = si.get(coff); sym.gmi = .wrap(@intCast(sym_gop.index)); sym.flags.type = opts.type; sym.flags.dll_storage_class = opts.dll_storage_class; - sym_gop.value_ptr.* = si; + sym_gop.value_ptr.* = .{ + .si = si, + .lib_name = lib_name, + }; coff.synth_prog_node.increaseEstimatedTotalItems(1); log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si }); @@ -2807,16 +2818,15 @@ fn getOrPutGlobalSymbol( } 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; + if (coff.globals.get( + coff.getString(name).unwrap() orelse return .null, + )) |global| if (global.si.get(coff).ni != .none) return global.si; return .null; } pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { const gop = try coff.getOrPutGlobalSymbol(opts); - return gop.value_ptr.*; + return gop.value_ptr.si; } pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void { @@ -3121,9 +3131,9 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void { var buf: [15]u8 = undefined; const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { - const gn = sym.gmi.globalName(coff); + const name = sym.gmi.name(coff); break :blk .{ - try coff.getOrPutSymbolName(gn.name.toSlice(coff), gn.name), + try coff.getOrPutSymbolName(name.toSlice(coff), name), @intFromBool(sym.flags.weak_external_strat != .none), if (Symbol.Index.text.get(coff).section_number == sym.section_number) .FUNCTION @@ -3735,7 +3745,7 @@ fn addRelocAssumeCapacity( const header = loc_sn.header(coff); const old_num_relocations = coff.targetLoad(&header.number_of_relocations); const new_num_relocations = old_num_relocations + 1; - const new_size = new_num_relocations * std.coff.Relocation.sizeOf(); + const new_size = @as(u32, new_num_relocations) * std.coff.Relocation.sizeOf(); coff.targetStore(&header.number_of_relocations, new_num_relocations); if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr| @@ -4529,17 +4539,16 @@ fn loadObject( .external => { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff), - .lib_name = null, }); // TODO: What if the same symbol is incorrectly defined twice in this obj? // Would need to mark this global as pending, or notice it later when .ni != none - if (!global_gop.found_existing or global_gop.value_ptr.get(coff).ni == .none) { - symbol.si = global_gop.value_ptr.*; + if (!global_gop.found_existing or global_gop.value_ptr.si.get(coff).ni == .none) { + symbol.si = global_gop.value_ptr.si; break :comdat .include; } - break :existing global_gop.value_ptr.*; + break :existing global_gop.value_ptr.si; }, }; @@ -4736,7 +4745,7 @@ fn loadObject( }, .weak_external => |alias_index| { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); - symbol.si = global_gop.value_ptr.*; + symbol.si = global_gop.value_ptr.si; if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { const sym = symbol.si.get(coff); const alias = pending_symbols.getPtr(alias_index) orelse @@ -4775,9 +4784,16 @@ fn loadObject( }, .external => |value| { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); - symbol.si = global_gop.value_ptr.*; + symbol.si = global_gop.value_ptr.si; if (global_gop.found_existing) - return coff.failMultipleDefinitions(path, member_name, symbol.name, index, global_gop.value_ptr.*, .none); + return coff.failMultipleDefinitions( + path, + member_name, + symbol.name, + index, + global_gop.value_ptr.si, + .none, + ); break :sym value; }, else => unreachable, @@ -4804,11 +4820,18 @@ fn loadObject( .external => { assert(index != section.comdat_psi.unwrap()); const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); - symbol.si = global_gop.value_ptr.*; + symbol.si = global_gop.value_ptr.si; const sym = symbol.si.get(coff); if (global_gop.found_existing and sym.ni != .none) - return coff.failMultipleDefinitions(path, member_name, symbol.name, index, global_gop.value_ptr.*, .none); + return coff.failMultipleDefinitions( + path, + member_name, + symbol.name, + index, + global_gop.value_ptr.si, + .none, + ); }, .weak_external, .weak_external_aux, @@ -4881,7 +4904,7 @@ fn loadObject( switch (symbol.value) { .external => |size| { const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); - symbol.si = global_gop.value_ptr.*; + symbol.si = global_gop.value_ptr.si; if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) { const sym = symbol.si.get(coff); sym.setExtra(.{ .size = @max(sym.size(), size) }); @@ -5743,7 +5766,9 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { num_full_notes + @intFromBool(num_unique_references > max_notes), ); const target_sym = target.get(coff); - try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.globalName(coff).name.toSlice(coff)}); + try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.name(coff).toSlice(coff)}); + + // TODO: If lib_name is set, show the user var prev_loc_si: Symbol.Index = .null; for (undef_indices.items[start_i .. i + 1]) |reference_i| { @@ -5774,9 +5799,9 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { if (section.comdat_si != .null) { const comdat_sym = section.comdat_si.get(coff); const comdat_name = if (comdat_sym.gmi != .none) - comdat_sym.gmi.globalName(coff).name.toSlice(coff) + comdat_sym.gmi.name(coff).toSlice(coff) else - coff.input_symbols.items[@intFromEnum(comdat_sym.extra.isli)].name.toSlice(coff); + comdat_sym.extra.isli.name(coff).toSlice(coff); err.addNote("referenced by input COMDAT section '{s}={s}' '{f}{f}'", .{ section_name, @@ -5793,14 +5818,14 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void { } } else { err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{ - loc_sym.gmi.globalName(coff).name.toSlice(coff), + loc_sym.gmi.name(coff).toSlice(coff), other_ioi.path(coff).fmtEscapeString(), fmtMemberNameString(other_ioi.memberName(coff)), }); } }, .import_thunk => |gmi| err.addNote("referenced by import thunk for '{s}'", .{ - gmi.globalName(coff).name.toSlice(coff), + gmi.name(coff).toSlice(coff), }), inline .nav, .uav, @@ -5959,7 +5984,7 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { if (coff.exports_complete and coff.global_pending_index < coff.globals.count()) { const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index); const sub_prog_node = coff.synth_prog_node.start( - gmi.globalName(coff).name.toSlice(coff), + gmi.name(coff).toSlice(coff), 0, ); defer sub_prog_node.end(); @@ -6138,7 +6163,7 @@ fn idleProgNode( coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff), }) catch &name; }, - .import_thunk => |gmi| gmi.globalName(coff).name.toSlice(coff), + .import_thunk => |gmi| gmi.name(coff).toSlice(coff), .nav => |nmi| { const ip = &coff.base.comp.zcu.?.intern_pool; break :name ip.getNav(nmi.navIndex(coff)).fqn.toSlice(ip); @@ -6216,7 +6241,6 @@ fn flushUav( } fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !void { - const gn = gmi.globalName(coff); const si = gmi.symbol(coff); const sym = si.get(coff); const alias_sym = alias_si.get(coff); @@ -6224,11 +6248,11 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v assert(sym.loc_relocs == .none); log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{ - gn.name.toSlice(coff), - gn.lib_name.toSlice(coff), + gmi.name(coff).toSlice(coff), + gmi.libName(coff).toSlice(coff), si, alias_si, - if (alias_sym.gmi != .none) alias_sym.gmi.globalName(coff).name.toSlice(coff) else null, + if (alias_sym.gmi != .none) alias_sym.gmi.name(coff).toSlice(coff) else null, }); var ri = sym.target_relocs; @@ -6250,7 +6274,7 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v alias_sym.target_relocs = sym.target_relocs; sym.target_relocs = .none; sym.gmi = alias_sym.gmi; - coff.globals.values()[gmi.unwrap().?] = alias_si; + coff.globals.values()[gmi.unwrap().?].si = alias_si; // Only apply the new relocs try alias_si.applyTargetRelocs(coff, prev_target_relocs); } @@ -6258,12 +6282,18 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const comp = coff.base.comp; const gpa = comp.gpa; - const gn = gmi.globalName(coff); + const name = gmi.name(coff); const si = gmi.symbol(coff); log.debug( "flushGlobal({s}, {?s}) = n{d} {d}@{d}", - .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), si.get(coff).ni, si, si.get(coff).section_number }, + .{ + name.toSlice(coff), + gmi.libName(coff).toSlice(coff), + si.get(coff).ni, + si, + si.get(coff).section_number, + }, ); if (!coff.isImage()) { @@ -6271,7 +6301,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { if (coff.isArchive() and si.get(coff).ni != .none) try coff.ensureMemberSymbol( coff.getNode(Node.known.zcu_member).archive_member, - gn.name, + name, ); return true; @@ -6292,18 +6322,18 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const import: Import = import: { const sym = si.get(coff); - const global_name = gn.name.toSlice(coff); - const imp_match = std.mem.startsWith(u8, global_name, imp_prefix); + const name_slice = name.toSlice(coff); + const imp_match = std.mem.startsWith(u8, name_slice, imp_prefix); // Globals may have the __imp_ prefix already if they are undef externals from another input. assert(sym.flags.dll_storage_class != .dllexport); const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport) - .{ gn.name, imp_match } + .{ name, imp_match } else name: { - try coff.ensureUnusedStringCapacity(imp_prefix.len + global_name.len); - const name = try std.fmt.allocPrint(gpa, imp_prefix ++ "{s}", .{global_name}); - defer gpa.free(name); - break :name .{ coff.getOrPutStringAssumeCapacity(name), true }; + try coff.ensureUnusedStringCapacity(imp_prefix.len + name_slice.len); + const imp_name = try std.fmt.allocPrint(gpa, imp_prefix ++ "{s}", .{name_slice}); + defer gpa.free(imp_name); + break :name .{ coff.getOrPutStringAssumeCapacity(imp_name), true }; }; const opt_alt_search_name = coff.alternate_names.get(search_name); @@ -6317,7 +6347,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { .anti_dependency => return comp.link_diags.fail( // TODO: Figure out what the purpose of this is "TODO support anti_dependency weak external: {s}", - .{gn.name.toSlice(coff)}, + .{name.toSlice(coff)}, ), }, else => true, @@ -6336,7 +6366,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const member = &coff.input_archive_members.items[@intFromEnum(archive_sym.iami)]; member: switch (member.content) { .object => if (!member.flags.is_loaded) { - if (gn.lib_name.unwrap()) |lib_name| + if (gmi.libName(coff).unwrap()) |lib_name| if (!std.ascii.eqlIgnoreCase( lib_name.toSlice(coff), member.iai.path(coff).stem(), @@ -6349,32 +6379,32 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { return false; }, .import => |import| { - if (gn.lib_name.unwrap()) |lib_name| + if (gmi.libName(coff).unwrap()) |lib_name| if (!std.ascii.eqlIgnoreCase( import.lib_name.toSlice(coff), lib_name.toSlice(coff), )) break :member; - const name: String.Optional = name: switch (import.name_type) { + const imp_name: String.Optional = name: switch (import.name_type) { .NAME, .NAME_NOPREFIX, .NAME_UNDECORATE, => |tag| { const symbol_name: []const u8 = import.symbol_name.toSlice(coff); - const end_match = std.mem.endsWith(u8, global_name, symbol_name); - const len_delta = global_name.len -% symbol_name.len; + const end_match = std.mem.endsWith(u8, name_slice, symbol_name); + const len_delta = name_slice.len -% symbol_name.len; if (!end_match or (!imp_match and len_delta != 0) or (imp_match and len_delta != imp_prefix.len)) return comp.link_diags.fail( "global '{s}' has mismatched symbol name in import header: '{s}'", .{ - gn.name.toSlice(coff), + name.toSlice(coff), import.symbol_name.toSlice(coff), }, ); - const name = if (tag == .NAME) import.symbol_name else undecorated: { + const imp_name = if (tag == .NAME) import.symbol_name else undecorated: { var imp_name = std.mem.trimStart(u8, symbol_name, "?@_"); if (tag == .NAME_UNDECORATE) imp_name = std.mem.sliceTo(imp_name, '@'); @@ -6383,7 +6413,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { break :undecorated coff.getOrPutStringAssumeCapacity(imp_name); }; - break :name name.toOptional(); + break :name imp_name.toOptional(); }, .ORDINAL => break :name .none, else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}), @@ -6391,7 +6421,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { break :import .{ .lib_name = import.lib_name, - .name = name, + .name = imp_name, .ordinal_hint = import.import_ordinal_hint, .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr, }; @@ -6414,7 +6444,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const alias_gop = try coff.getOrPutGlobalSymbol(.{ .name = sym.value.weak_alias_name.toSlice(coff), }); - try coff.aliasGlobal(gmi, alias_gop.value_ptr.*); + try coff.aliasGlobal(gmi, alias_gop.value_ptr.si); return true; }, else => {}, @@ -6422,8 +6452,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { // If there was an object that had the alternate name, we've attempted to load it if (opt_alt_search_name) |alt_search_name| { - if (coff.globals.get(.{ .name = alt_search_name, .lib_name = .none })) |alias_si| { - try coff.aliasGlobal(gmi, alias_si); + if (coff.globals.get(alt_search_name)) |alias_global| { + try coff.aliasGlobal(gmi, alias_global.si); return true; } } @@ -6432,9 +6462,9 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification, // which are not in the implib. if (sym.flags.type != .unknown) { - if (gn.lib_name.unwrap()) |lib_name| break :import .{ + if (gmi.libName(coff).unwrap()) |lib_name| break :import .{ .lib_name = lib_name, - .name = gn.name.toOptional(), + .name = name.toOptional(), .ordinal_hint = 0, .kind = if (sym.flags.type == .code) .thunk else .iat_ptr, }; @@ -6525,7 +6555,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { log.debug( "flushGlobalImport({s}, {?s}, {d}, {s})", - .{ gn.name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name }, + .{ name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name }, ); const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{ @@ -6544,11 +6574,11 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { 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 opt_imp_name = import.name.toSlice(coff); + const opt_import_hint_name_index = if (opt_imp_name) |imp_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), + import_hint_name_align.forward(import_hint_name_index + 2 + imp_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; @@ -6558,8 +6588,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { 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])); ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian); - @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_name.?.len], opt_name.?); - @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_name.?.len ..], 0); + @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_imp_name.?.len], opt_imp_name.?); + @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_imp_name.?.len ..], 0); break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index; } else 0; @@ -6687,7 +6717,7 @@ fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol { if (entry_si != .null) { log.debug( "entry({s}, {d})", - .{ entry_si.get(coff).gmi.globalName(coff).name.toSlice(coff), entry_si }, + .{ entry_si.get(coff).gmi.name(coff).toSlice(coff), entry_si }, ); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -7379,10 +7409,7 @@ fn updateExportsInner( const name = @"export".opts.name.toSlice(ip); // TODO: add an errMsg if this conflicts with an existing symbol - const export_si = try coff.globalSymbol(.{ - .name = name, - .lib_name = null, - }); + const export_si = try coff.globalSymbol(.{ .name = name }); const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; @@ -7605,6 +7632,8 @@ fn printSymbol( } else { try w.writeAll("| "); try coff.printNodeName(w, tid, node); + if (sym.flags.extra_tag == .isli) + try w.print(" | {s}", .{sym.extra.isli.name(coff).toSlice(coff)}); try w.writeByte('\n'); } } @@ -7617,9 +7646,8 @@ fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalNam 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.writeAll(data.gmi.name(data.coff).toSlice(data.coff)); + if (data.gmi.libName(data.coff).unwrap()) |lib_name| try w.print("({s})", .{lib_name.toSlice(data.coff)}); } @@ -7645,7 +7673,7 @@ fn printNodeName( if (is.comdat_si != .null) { const comdat_sym = is.comdat_si.get(coff); const comdat_name = if (comdat_sym.gmi != .none) - comdat_sym.gmi.globalName(coff).name.toSlice(coff) + comdat_sym.gmi.name(coff).toSlice(coff) else coff.input_symbols.items[@intFromEnum(comdat_sym.extra.isli)].name.toSlice(coff); @@ -7664,10 +7692,9 @@ fn printNodeName( }), .import_thunk, => |gmi| { - const gn = gmi.globalName(coff); try w.writeByte('('); - if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); - try w.print("{s})", .{gn.name.toSlice(coff)}); + if (gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); + try w.print("{s})", .{gmi.name(coff).toSlice(coff)}); }, .nav => |nmi| { const zcu = coff.base.comp.zcu.?; @@ -7695,10 +7722,9 @@ fn printNodeName( .builtin => |si| { const sym = si.get(coff); if (sym.gmi != .none) { - const gn = sym.gmi.globalName(coff); try w.writeByte('('); - if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); - try w.print("{s})", .{gn.name.toSlice(coff)}); + if (sym.gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); + try w.print("{s})", .{sym.gmi.name(coff).toSlice(coff)}); } }, } -- 2.54.0 From f5a911290e97435a09d46113551e07ace7367641 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 23 Jun 2026 00:20:06 -0400 Subject: [PATCH 91/94] test/link: add explicit lib name test --- test/link.zig | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/link.zig b/test/link.zig index c21030db764a2b650f9e80e51514d7a2490d7c40..1dc75f4d0df843be0431092a06dbdcb067cd0ab1 100644 --- a/test/link.zig +++ b/test/link.zig @@ -277,6 +277,64 @@ pub fn addCases(ctx: *LinkContext) void { }); } } + + if (ctx.includeTest("explicit-extern-lib-name")) |case| { + // TODO: Lld.zig does not look at explicit inputs to resolve explicit extern lib names + if (ctx.use_llvm) return; + + const lib1 = case.addLibrary(.dynamic, .{ + .name = "lib1", + .name_target = false, + .zig_source_bytes = + \\export fn foo() u8 { + \\ return 43; + \\} + , + }); + + const lib2 = case.addLibrary(.dynamic, .{ + .name = "lib2", + .name_target = false, + .zig_source_bytes = + \\export fn foo() u8 { + \\ return 42; + \\} + , + }); + + const lib3 = case.addLibrary(.static, .{ + .name = "lib3", + .zig_source_bytes = + \\extern fn foo() u8; + \\export fn callFoo() u8 { + \\ return foo(); + \\} + , + }); + + const exe = case.addExecutable(.{ + .name = "test", + .zig_source_bytes = + \\extern "explicit-extern-lib-name-lib2" fn foo() u8; + \\extern fn callFoo() u8; + \\pub fn main() !u8 { + \\ return foo() + callFoo(); + \\} + , + }); + exe.root_module.linkLibrary(lib1); + exe.root_module.linkLibrary(lib2); + // exe.root_module.addLibraryPath(.{ + // .generated = .{ + // .index = lib2.getEmittedBin().generated.index, + // .up = 1, + // }, + // }); + exe.root_module.linkLibrary(lib3); + + const run = case.addRunArtifact(exe); + run.addCheck(.{ .expect_term = .{ .exited = 84 } }); + } } const LinkContext = @import("tests.zig").LinkContext; -- 2.54.0 From be7b9dec521f36909643c7084c16c284968a7db3 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 23 Jun 2026 22:04:11 -0400 Subject: [PATCH 92/94] tests: enable selfhosted x86_64 c-abi targets --- test/tests.zig | 58 +++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 75810c0e17042634e335a1712c46338636987501..6166444b9b33bdcafc034cfb26bc55aa9c48a916 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2021,35 +2021,35 @@ const c_abi_targets = blk: { }, }, - //.{ - // .target = .{ - // .cpu_arch = .x86_64, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // .use_llvm = false, - // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, - //}, - //.{ - // .target = .{ - // .cpu_arch = .x86_64, - // .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 }, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // .use_llvm = false, - // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, - //}, - //.{ - // .target = .{ - // .cpu_arch = .x86_64, - // .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 }, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // .use_llvm = false, - // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, - //}, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + .use_llvm = false, + .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 }, + .os_tag = .windows, + .abi = .gnu, + }, + .use_llvm = false, + .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 }, + .os_tag = .windows, + .abi = .gnu, + }, + .use_llvm = false, + .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, + }, .{ .target = .{ .cpu_arch = .x86_64, -- 2.54.0 From d94b3e7691f15b8bc6993cc6999d0f439255ed80 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 23 Jun 2026 22:05:25 -0400 Subject: [PATCH 93/94] Coff: fix incorrect section offset calculations when writing section relocation tables --- src/link/Coff.zig | 31 ++++++++++++++++++++---------- test/link/snapshots/.gitattributes | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 5ef4a76e228f04b7434993a7f148081d81151645..ad156c5f417947a46ebdca801d27717ab3ca06ca 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1110,7 +1110,7 @@ pub const Symbol = struct { if (reloc.loc != si) break; if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore( &entry.virtual_address, - @intCast(coff.computeSymbolSectionOffset(sym) + reloc.offset), + @intCast(coff.computeSymbolSectionOffset(sym, .image) + reloc.offset), ); try reloc.apply(coff); } @@ -1442,7 +1442,7 @@ pub const Reloc = extern struct { .SECREL => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), + @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend), target_endian, ), }, @@ -1482,7 +1482,7 @@ pub const Reloc = extern struct { .SECREL => std.mem.writeInt( u32, loc_slice[0..4], - @intCast(coff.computeSymbolSectionOffset(target_sym) + reloc.addend), + @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend), target_endian, ), }, @@ -1496,7 +1496,7 @@ pub const Reloc = extern struct { // TODO: If this was the last reloc causing something to be in the symbol table, we should remove // the symbol table entry (and unset sti). That will require flushSymbolTableIndex on the // swapped symbol if we exchange indices - unreachable; + @panic("TODO implement symbol table reloc deletions"); } switch (reloc.prev) { @@ -2443,7 +2443,12 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 { const offset, _ = ni.location(&coff.mf).resolve(&coff.mf); return @intCast(parent_rva + offset); } -fn computeSymbolSectionOffset(coff: *Coff, sym: *const Symbol) u32 { + +fn computeSymbolSectionOffset( + coff: *Coff, + sym: *const Symbol, + relative_to: enum { image, pseudo }, +) u32 { var section_offset: u32 = sym.nodeOffset(coff); var parent_ni = sym.ni; while (true) { @@ -2452,10 +2457,14 @@ fn computeSymbolSectionOffset(coff: *Coff, sym: *const Symbol) u32 { parent_ni = parent_ni.parent(&coff.mf); switch (coff.getNode(parent_ni)) { else => unreachable, - .image_section, .pseudo_section => return section_offset, - .object_section => {}, + .image_section => break, + .pseudo_section => if (relative_to == .pseudo) break, + .object_section, + => {}, } } + + return section_offset; } pub inline fn targetEndian(_: *const Coff) std.lang.Endian { @@ -3279,7 +3288,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void { => unreachable, else => switch (coff.getNode(sym.ni)) { .image_section => 0, - else => coff.computeSymbolSectionOffset(sym), + else => coff.computeSymbolSectionOffset(sym, .image), }, }); @@ -3328,10 +3337,12 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void { const si = isi.symbol(coff); si.node(coff).writer(&coff.mf, gpa, &nw); defer nw.deinit(); - log.debug("flushInputSection({f}{f}, {s})", .{ + log.debug("flushInputSection({f}{f}, {s}, {d}, n{d})", .{ path, fmtMemberNameString(ioi.memberName(coff)), - isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff), + si.get(coff).section_number.name(coff).toSlice(coff), + si, + si.node(coff), }); if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; diff --git a/test/link/snapshots/.gitattributes b/test/link/snapshots/.gitattributes index 625449502ba4b4d7c229c6ccee42e3ec41cc780c..699baaf03a7db7fe3ff3dcc2c4986c32e1aa52c3 100644 --- a/test/link/snapshots/.gitattributes +++ b/test/link/snapshots/.gitattributes @@ -1 +1 @@ -* -text +*.dmp eol=lf -- 2.54.0 From 594b3faaa34f6f464a5c543489cf57fb89070999 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 23 Jun 2026 22:06:17 -0400 Subject: [PATCH 94/94] MappedFile: explicitly write the mtime when flushing, as mapped file writes don't do this automatically on windows --- src/link/MappedFile.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 03b92315aa32ebf8a9baa8a4960354292a5d4634..74c1ce432999043d8fe5ad0331d8ba4128973abd 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1314,11 +1314,12 @@ pub fn unmap(mf: *MappedFile) void { } pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void { - mf.memory_map.write(mf.io) catch |err| switch (err) { + mf.flushInner() catch |err| switch (err) { error.Canceled => |e| return e, error.WouldBlock, // file was not opened as non-blocking error.NotOpenForWriting, // we definitely opened the file for writing + error.ReadOnlyFileSystem, => { mf.io_err = error.Unexpected; return error.MappedFileIo; @@ -1331,6 +1332,11 @@ pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void { }; } +fn flushInner(mf: *MappedFile) (Io.File.WritePositionalError || Io.File.SetTimestampsError)!void { + try mf.memory_map.write(mf.io); + if (is_windows) try mf.memory_map.file.setTimestampsNow(mf.io); +} + fn verify(mf: *MappedFile) void { const root = Node.Index.root.get(mf); assert(root.parent == .none); -- 2.54.0