From 5a4fda7e36e9862d6c0fd8fe15d7bd5cefa2f6e9 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 12 Sep 2026 15:49:50 -0400 Subject: [PATCH 1/5] Coff: use a section merge instead of manually rewriting attributes for .tls --- src/link/Coff.zig | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 11a2018056db7642d5a95f4b3c6dfc462c2aa78d..2e792fe53640dc6e68679206d51ad0f27411e15b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -2263,6 +2263,11 @@ pub fn initBuiltins(coff: *Coff) !void { } defer coff.flushSectionMerges() catch unreachable; + if (coff.isImage()) { + // In images, the .tls section is a read-only template + try coff.section_merges.put(gpa, .@".tls", .@".rdata"); + } + if (coff.isImage() and target.isMinGW() and comp.config.link_libc) { try coff.symbols.ensureUnusedCapacity(gpa, 8); try coff.globals.ensureUnusedCapacity(gpa, 2); @@ -3543,20 +3548,13 @@ 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; - attr.write = false; - break :attr attr; - } else attributes; const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @fromBackingInt(@intCast(object_section_gop.index)); 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); + const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, attributes)).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); const parent_ni = parent.node(coff); @@ -3608,7 +3606,7 @@ fn objectSectionMapIndex( name, .object, .fromFlags(sym.section_number.header(coff).flags), - effective_attributes, + attributes, ); return osmi; -- 2.54.0 From 96c654941c98017e9e59fe38e06d9a334fb6c80c Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 19 Sep 2026 13:25:56 -0400 Subject: [PATCH 2/5] Coff: rework global creation to avoid invalidating strings that were themselves used as arguments --- src/link/Coff.zig | 267 ++++++++++++++++++++++++++-------------------- 1 file changed, 152 insertions(+), 115 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2e792fe53640dc6e68679206d51ad0f27411e15b..c5e29ce7537649802fc86a639335cf2ffa63af54 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -278,7 +278,7 @@ pub const Node = union(enum) { return coff.globals.values()[gmi.unwrap().?].si; } - pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) String.Optional { + pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) ExternLibName { return coff.globals.values()[gmi.unwrap().?].lib_name; } }; @@ -864,7 +864,7 @@ pub const Section = struct { pub const Global = struct { si: Symbol.Index, - lib_name: String.Optional, + lib_name: ExternLibName, }; pub const WeakExternalStrat = enum(u3) { @@ -2781,37 +2781,43 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String { return @fromBackingInt(@intCast(gop.key_ptr.*)); } -const GlobalOptions = struct { - name: []const u8, - lib_name: ?[]const u8 = null, - type: Symbol.Type = .unknown, - dll_storage_class: Symbol.DllStorageClass = .default, +const ExternLibName = union(enum) { + // Symbol can be supplied from anywhere + any, + // Symbol can be supplied by any LibC input + lib_c, + // Symbol must be supplied by a specific input + explicit: String, }; +const FmtExternLibName = struct { coff: *Coff, lib_name: ExternLibName }; + +fn fmtExternLibName(coff: *Coff, lib_name: ExternLibName) std.fmt.Alt(FmtExternLibName, formatExternLibName) { + return .{ .data = .{ .coff = coff, .lib_name = lib_name } }; +} + +fn formatExternLibName(data: FmtExternLibName, w: *Io.Writer) Io.Writer.Error!void { + try w.writeAll(switch (data.lib_name) { + .any => "*", + .lib_c => "c", + .explicit => |str| str.toSlice(data.coff), + }); +} + fn getOrPutGlobalSymbol( coff: *Coff, - opts: GlobalOptions, + opts: struct { + name: String, + lib_name: ExternLibName = .any, + type: Symbol.Type = .unknown, + dll_storage_class: Symbol.DllStorageClass = .default, + }, ) !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: 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 - 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 .none; - } - - break :lib_name (try coff.getOrPutString(lib_name)).toOptional(); - } else .none; - - const sym_gop = try coff.globals.getOrPut(gpa, try coff.getOrPutString(opts.name)); + const sym_gop = try coff.globals.getOrPut(gpa, opts.name); if (!sym_gop.found_existing) { const si = coff.addSymbolAssumeCapacity(); const sym = si.get(coff); @@ -2820,11 +2826,11 @@ fn getOrPutGlobalSymbol( sym.flags.dll_storage_class = opts.dll_storage_class; sym_gop.value_ptr.* = .{ .si = si, - .lib_name = lib_name, + .lib_name = opts.lib_name, }; coff.synth_prog_node.increaseEstimatedTotalItems(1); - log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si }); + log.debug("globalSymbol({s}, {f}) = {d}", .{ opts.name.toSlice(coff), fmtExternLibName(coff, opts.lib_name), si }); } return sym_gop; @@ -2837,8 +2843,36 @@ fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index { return .null; } -pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index { - const gop = try coff.getOrPutGlobalSymbol(opts); +pub fn globalSymbol(coff: *Coff, opts: struct { + name: []const u8, + lib_name: ?[]const u8 = null, + type: Symbol.Type = .unknown, + dll_storage_class: Symbol.DllStorageClass = .default, +}) !Symbol.Index { + const comp = coff.base.comp; + const lib_name: ExternLibName = 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; + break :lib_name .lib_c; + } + + try coff.ensureManyUnusedStringCapacity(2, opts.name.len + lib_name.len); + break :lib_name .{ .explicit = coff.getOrPutStringAssumeCapacity(lib_name) }; + } else .any; + + try coff.ensureUnusedStringCapacity(opts.name.len); + const gop = try coff.getOrPutGlobalSymbol(.{ + .name = coff.getOrPutStringAssumeCapacity(opts.name), + .lib_name = lib_name, + .type = opts.type, + .dll_storage_class = opts.dll_storage_class, + }); return gop.value_ptr.si; } @@ -3547,13 +3581,16 @@ fn objectSectionMapIndex( attributes: ObjectSectionAttributes, ) !Node.ObjectSectionMapIndex { const gpa = coff.base.comp.gpa; - const name_slice = name.toSlice(coff); const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @fromBackingInt(@intCast(object_section_gop.index)); 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 name_slice = name.toSlice(coff); + const parent_name_slice = coff.objectSectionParentName(name_slice); + const parent_name = if (parent_name_slice.len < name_slice.len) parent_name: { + try coff.ensureUnusedStringCapacity(parent_name_slice.len); + break :parent_name coff.getOrPutStringAssumeCapacity(name.toSlice(coff)[0..parent_name_slice.len]); + } else name; const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, attributes)).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -4540,7 +4577,7 @@ fn loadObject( }, .external => { const global_gop = try coff.getOrPutGlobalSymbol(.{ - .name = symbol.name.toSlice(coff), + .name = symbol.name, }); // TODO: What if the same symbol is incorrectly defined twice in this obj? @@ -4747,7 +4784,7 @@ fn loadObject( continue; }, .weak_external => |alias_index| { - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name }); 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); @@ -4786,7 +4823,7 @@ fn loadObject( break :sym value; }, .external => |value| { - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name }); symbol.si = global_gop.value_ptr.si; if (global_gop.found_existing) return coff.failMultipleDefinitions( @@ -4822,7 +4859,7 @@ fn loadObject( }, .external => { assert(index != section.comdat_psi.unwrap()); - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name }); symbol.si = global_gop.value_ptr.si; const sym = symbol.si.get(coff); @@ -4906,7 +4943,7 @@ fn loadObject( assert(symbol.section_number == .UNDEFINED); switch (symbol.value) { .external => |size| { - const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) }); + const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name }); 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); @@ -6244,9 +6281,9 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v assert(sym.section_number == .UNDEFINED); assert(sym.loc_relocs == .none); - log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{ + log.debug("aliasGlobal({s}, {f}) {d}->{d} ({?s})", .{ gmi.name(coff).toSlice(coff), - gmi.libName(coff).toSlice(coff), + fmtExternLibName(coff, gmi.libName(coff)), si, alias_si, if (alias_sym.gmi != .none) alias_sym.gmi.name(coff).toSlice(coff) else null, @@ -6284,10 +6321,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const si = gmi.symbol(coff); log.debug( - "flushGlobal({s}, {?s}) = n{d} {d}@{d}", + "flushGlobal({s}, {f}) = n{d} {d}@{d}", .{ name.toSlice(coff), - gmi.libName(coff).toSlice(coff), + fmtExternLibName(coff, gmi.libName(coff)), si.get(coff).ni, si, si.get(coff).section_number, @@ -6362,68 +6399,72 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { while (true) { const archive_sym = &coff.input_archive_symbols.items[@backingInt(iter)]; const member = &coff.input_archive_members.items[@backingInt(archive_sym.iami)]; - member: switch (member.content) { - .object => if (!member.flags.is_loaded) { - if (gmi.libName(coff).unwrap()) |lib_name| - if (!std.ascii.eqlIgnoreCase( - lib_name.toSlice(coff), - member.iai.path(coff).stem(), - )) break :member; + const lib_name_matches = match: switch (gmi.libName(coff)) { + .any => true, + .lib_c => { + // TODO: Need a flag to indicate if this member is from a libc archive + const is_libc_archive = true; - // 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; + break :match is_libc_archive; }, - .import => |import| { - if (gmi.libName(coff).unwrap()) |lib_name| - if (!std.ascii.eqlIgnoreCase( - import.lib_name.toSlice(coff), - lib_name.toSlice(coff), - )) break :member; + .explicit => |lib_name| std.ascii.eqlIgnoreCase( + lib_name.toSlice(coff), + member.iai.path(coff).stem(), + ), + }; - 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, 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}'", - .{ - name.toSlice(coff), - import.symbol_name.toSlice(coff), - }, - ); + if (lib_name_matches) { + switch (member.content) { + .object => if (!member.flags.is_loaded) { + // 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| { + 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, 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}'", + .{ + name.toSlice(coff), + import.symbol_name.toSlice(coff), + }, + ); - 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, '@'); + 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, '@'); - try coff.ensureUnusedStringCapacity(imp_name.len); - break :undecorated coff.getOrPutStringAssumeCapacity(imp_name); - }; + try coff.ensureUnusedStringCapacity(imp_name.len); + break :undecorated coff.getOrPutStringAssumeCapacity(imp_name); + }; - break :name imp_name.toOptional(); - }, - .ORDINAL => break :name .none, - else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}), - }; + break :name imp_name.toOptional(); + }, + .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 = imp_name, - .ordinal_hint = import.import_ordinal_hint, - .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr, - }; - }, + break :import .{ + .lib_name = import.lib_name, + .name = imp_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; @@ -6440,7 +6481,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { // 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.weak_alias_name.toSlice(coff), + .name = sym.value.weak_alias_name, }); try coff.aliasGlobal(gmi, alias_gop.value_ptr.si); return true; @@ -6460,12 +6501,15 @@ 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 (gmi.libName(coff).unwrap()) |lib_name| break :import .{ - .lib_name = lib_name, - .name = name.toOptional(), - .ordinal_hint = 0, - .kind = if (sym.flags.type == .code) .thunk else .iat_ptr, - }; + switch (gmi.libName(coff)) { + .explicit => |lib_name| break :import .{ + .lib_name = lib_name, + .name = name.toOptional(), + .ordinal_hint = 0, + .kind = if (sym.flags.type == .code) .thunk else .iat_ptr, + }, + else => {}, + } } return true; @@ -7660,8 +7704,10 @@ 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; 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)}); + switch (data.gmi.libName(data.coff)) { + .explicit => |lib_name| try w.print(" ({s}.dll)", .{lib_name.toSlice(data.coff)}), + else => {}, + } } fn printNodeName( @@ -7703,12 +7749,7 @@ fn printNodeName( inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{ smi.name(coff).toSlice(coff), }), - .import_thunk, - => |gmi| { - try w.writeByte('('); - if (gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name}); - try w.print("{s})", .{gmi.name(coff).toSlice(coff)}); - }, + .import_thunk => |gmi| try w.print("({f})", .{fmtGlobalName(coff, gmi)}), .nav => |nmi| { const zcu = coff.base.comp.zcu.?; const ip = &zcu.intern_pool; @@ -7734,11 +7775,7 @@ fn printNodeName( }), .builtin => |si| { const sym = si.get(coff); - if (sym.gmi != .none) { - try w.writeByte('('); - 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)}); - } + if (sym.gmi != .none) try w.print("({f})", .{fmtGlobalName(coff, sym.gmi)}); }, } } -- 2.54.0 From 6ec487d91da45787cf94f02997fe195aac58aef5 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 19 Sep 2026 15:35:06 -0400 Subject: [PATCH 3/5] Coff: fix several lifetime issues with slices into `string_bytes` --- src/link/Coff.zig | 118 +++++++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index c5e29ce7537649802fc86a639335cf2ffa63af54..eb8f89717f9f2dd2dfa6dec437ba0cd0485b5b39 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -647,15 +647,12 @@ pub const SymbolTable = struct { }; pub const SymbolName = union(enum) { - short: []const u8, + short: [8]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); - }, + .short => |s| field.* = s, .long => |l| { @memset(field[0..4], 0); std.mem.writePackedInt(u32, field[4..], 0, @backingInt(l), coff.targetEndian()); @@ -2728,29 +2725,42 @@ fn getString(coff: *Coff, string: []const u8) String.Optional { return .none; } -/// 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 { +/// If the name does not fit within inline storage, adds it to the symbol table string table. +fn getOrPutSymbolName(coff: *Coff, name: union(enum) { + slice: []const u8, + string: String, +}) !SymbolTable.SymbolName { assert(!coff.isImage()); const gpa = coff.base.comp.gpa; + const slice = switch (name) { + .slice => |s| s, + .string => |s| s.toSlice(coff), + }; + + if (slice.len > header_name_max_len) { + const string = switch (name) { + .slice => |s| try coff.getOrPutString(s), + .string => |s| s, + }; - 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) { const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1]; string_gop.value_ptr.* = @fromBackingInt(@intCast(string_index)); - try coff.symbol_table.strings_ni.resizeLeaf(gpa, &coff.mf, string_index + name.len + 1); - const slice = coff.symbol_table.strings_ni.slice(&coff.mf); - @memcpy(slice[@intCast(string_index)..][0..name.len], name); - slice[@intCast(string_index + name.len)] = 0; + try coff.symbol_table.strings_ni.resizeLeaf(gpa, &coff.mf, string_index + slice.len + 1); + const storage = coff.symbol_table.strings_ni.slice(&coff.mf); + @memcpy(storage[@intCast(string_index)..][0..slice.len], slice); + storage[@intCast(string_index + slice.len)] = 0; } - break :name .{ .long = string_gop.value_ptr.* }; - } else .{ .short = name }; + return .{ .long = string_gop.value_ptr.* }; + } else { + var symbol_name: SymbolTable.SymbolName = .{ .short = undefined }; + @memcpy(symbol_name.short[0..slice.len], slice); + @memset(symbol_name.short[slice.len..], 0); + return symbol_name; + } } /// `len` does not include null terminators @@ -2862,7 +2872,7 @@ pub fn globalSymbol(coff: *Coff, opts: struct { break :lib_name .lib_c; } - try coff.ensureManyUnusedStringCapacity(2, opts.name.len + lib_name.len); + try coff.ensureManyUnusedStringCapacity(2, opts.name.len + lib_name.len + 2); break :lib_name .{ .explicit = coff.getOrPutStringAssumeCapacity(lib_name) }; } else .any; @@ -3146,7 +3156,7 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void { coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), @backingInt(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]); + @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice); 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 @@ -3175,9 +3185,8 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32) !void { const entry = coff.symbolTableEntryPtr(sti.*) orelse entry: { const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType = if (sym.gmi != .none) blk: { - const name = sym.gmi.name(coff); break :blk .{ - try coff.getOrPutSymbolName(name.toSlice(coff), name), + try coff.getOrPutSymbolName(.{ .string = sym.gmi.name(coff) }), @intFromBool(sym.flags.weak_external_strat != .none), if (Symbol.Index.text.get(coff).section_number == sym.section_number) .FUNCTION @@ -3186,7 +3195,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32) !void { }; } else blk: switch (coff.getNode(sym.ni.unwrap().?)) { .image_section => .{ - try coff.getOrPutSymbolName(&sym.section_number.header(coff).name, null), + try coff.getOrPutSymbolName(.{ .slice = &sym.section_number.header(coff).name }), 1, .NULL, }, @@ -3195,7 +3204,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32) !void { const ip = &zcu.intern_pool; const nav = ip.getNav(nmi.navIndex(coff)); break :blk .{ - try coff.getOrPutSymbolName(nav.fqn.toSlice(ip), null), + try coff.getOrPutSymbolName(.{ .slice = nav.fqn.toSlice(ip) }), 0, if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL, }; @@ -3204,7 +3213,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32) !void { var name_buf: [std.fmt.count("__anon_{d}", .{std.math.maxInt(u32)})]u8 = undefined; const name = std.mem.print(&name_buf, "__anon_{d}", .{umi}) catch unreachable; break :blk .{ - try coff.getOrPutSymbolName(name, null), + try coff.getOrPutSymbolName(.{ .slice = name }), 0, .NULL, }; @@ -3218,9 +3227,8 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32) !void { lazy_sym.kind, mi, }) catch unreachable; - const string = try coff.getOrPutString(name); break :blk .{ - try coff.getOrPutSymbolName(string.toSlice(coff), string), + try coff.getOrPutSymbolName(.{ .slice = name }), 0, if (tag == .lazy_code) .FUNCTION else .NULL, }; @@ -3451,8 +3459,8 @@ fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !S if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.SectionHeader, section); - const name_slice = name.toSlice(coff); if (coff.isImage()) { + const name_slice = name.toSlice(coff); @memcpy(section.name[0..name_slice.len], name_slice); @memset(section.name[name_slice.len..], 0); switch (coff.optionalHeaderPtr()) { @@ -3462,7 +3470,7 @@ fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !S ), } } else { - (try coff.getOrPutSymbolName(name_slice, name)).store(coff, §ion.name); + (try coff.getOrPutSymbolName(.{ .string = name })).store(coff, §ion.name); try coff.pendingSymbolTableEntry(si); } @@ -3585,12 +3593,21 @@ fn objectSectionMapIndex( const object_section_gop = try coff.object_section_table.getOrPut(gpa, name); const osmi: Node.ObjectSectionMapIndex = @fromBackingInt(@intCast(object_section_gop.index)); const sym = if (!object_section_gop.found_existing) sym: { - const name_slice = name.toSlice(coff); - const parent_name_slice = coff.objectSectionParentName(name_slice); - const parent_name = if (parent_name_slice.len < name_slice.len) parent_name: { - try coff.ensureUnusedStringCapacity(parent_name_slice.len); - break :parent_name coff.getOrPutStringAssumeCapacity(name.toSlice(coff)[0..parent_name_slice.len]); - } else name; + const name_slice, const parent_name = blk: { + var name_slice = name.toSlice(coff); + const parent_name_slice = coff.objectSectionParentName(name_slice); + const parent_name = if (parent_name_slice.len < name_slice.len) parent_name: { + try coff.ensureUnusedStringCapacity(parent_name_slice.len); + name_slice = name.toSlice(coff); + break :parent_name coff.getOrPutStringAssumeCapacity(name_slice[0..parent_name_slice.len]); + } else name; + + break :blk .{ name_slice, parent_name }; + }; + + coff.string_bytes.lockPointers(); + defer coff.string_bytes.unlockPointers(); + const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, attributes)).symbol(coff); try coff.nodes.ensureUnusedCapacity(gpa, 1); try coff.symbols.ensureUnusedCapacity(gpa, 1); @@ -5998,12 +6015,12 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } if (coff.pending_input) |pending_iami| { - const name_slice = pending_iami.member(coff).name.toSlice(coff); const sub_prog_node = coff.input_prog_node.start( - name_slice, + pending_iami.member(coff).name.toSlice(coff), 0, ); defer sub_prog_node.end(); + coff.pending_input = null; coff.flushInputMember(pending_iami) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -6011,7 +6028,7 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool { "linker failed to load archive member '{f}{f}': {t}", .{ pending_iami.member(coff).iai.path(coff), - fmtMemberNameString(name_slice), + fmtMemberNameString(pending_iami.member(coff).name.toSlice(coff)), e, }, ), @@ -6357,16 +6374,18 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const import: Import = import: { const sym = si.get(coff); - const name_slice = name.toSlice(coff); - const imp_match = std.mem.startsWith(u8, name_slice, imp_prefix); + const imp_match, const name_len = blk: { + const name_slice = name.toSlice(coff); + break :blk .{ std.mem.startsWith(u8, name_slice, imp_prefix), name_slice.len }; + }; // 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) .{ name, imp_match } else name: { - try coff.ensureUnusedStringCapacity(imp_prefix.len + name_slice.len); - const imp_name = try gpa.print(imp_prefix ++ "{s}", .{name_slice}); + try coff.ensureUnusedStringCapacity(imp_prefix.len + name_len); + const imp_name = try gpa.print(imp_prefix ++ "{s}", .{name.toSlice(coff)}); defer gpa.free(imp_name); break :name .{ coff.getOrPutStringAssumeCapacity(imp_name), true }; }; @@ -6429,6 +6448,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { .NAME_UNDECORATE, => |tag| { const symbol_name: []const u8 = import.symbol_name.toSlice(coff); + const name_slice = name.toSlice(coff); const end_match = std.mem.endsWith(u8, name_slice, symbol_name); const len_delta = name_slice.len -% symbol_name.len; if (!end_match or @@ -6520,10 +6540,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const target_endian = coff.targetEndian(); const addr_info = coff.targetAddrInfo(); - const lib_name = import.lib_name.toSlice(coff); + const lib_name_slice = import.lib_name.toSlice(coff); const gop = try coff.import_table.entries.getOrPutAdapted( gpa, - lib_name, + lib_name_slice, ImportTable.Adapter{ .coff = coff }, ); const import_hint_name_align: Alignment = .@"2"; @@ -6535,7 +6555,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2), ); const import_hint_name_table_len = - import_hint_name_align.forward(lib_name.len + ".dll".len + 1); + import_hint_name_align.forward(lib_name_slice.len + ".dll".len + 1); const idata_section_ni = coff.import_table.ni.parent(&coff.mf).unwrap().?; const import_lookup_table_ni = try idata_section_ni.addFloatingChild(gpa, &coff.mf, .{ .size = addr_info.size * 2, @@ -6570,9 +6590,9 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { .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); + @memcpy(import_hint_name_slice[0..lib_name_slice.len], lib_name_slice); + @memcpy(import_hint_name_slice[lib_name_slice.len..][0..".dll".len], ".dll"); + @memset(import_hint_name_slice[lib_name_slice.len + ".dll".len ..], 0); coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @fromBackingInt(@intCast(gop.index)) }); coff.nodes.appendAssumeCapacity(.{ .import_address_table = @fromBackingInt(@intCast(gop.index)) }); coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @fromBackingInt(@intCast(gop.index)) }); @@ -6597,7 +6617,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { log.debug( "flushGlobalImport({s}, {?s}, {d}, {s})", - .{ name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name }, + .{ name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name_slice }, ); const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{ -- 2.54.0 From 5dab6a4533dbb5222be5b6309eea25115659bacf Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 19 Sep 2026 15:35:47 -0400 Subject: [PATCH 4/5] objdump: instead of crashing when using --strings and there is no string table, print a message --- lib/compiler/objdump.zig | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig index c3507d9e66f3f616a50e454ae5350b4a5a50d2c3..94e3bec51b952c64c0b3065a34d54922e9b17ea4 100644 --- a/lib/compiler/objdump.zig +++ b/lib/compiler/objdump.zig @@ -828,19 +828,21 @@ const coff = struct { defer gpa.free(string_table); if (d.opts.strings) { - if (d.element(.@"table-header")) - try w.print( - \\String Table (0x{x} bytes): - \\ - , .{string_table.len}); + if (string_table.len > 0) { + if (d.element(.@"table-header")) + try w.print( + \\String Table (0x{x} bytes): + \\ + , .{string_table.len}); - var sr = Io.Reader.fixed(string_table[@sizeOf(u32)..]); - while (try sr.takeDelimiter(0)) |str| { - try w.writeAll(str); - try w.writeByte('\n'); - } + var sr = Io.Reader.fixed(string_table[@sizeOf(u32)..]); + while (try sr.takeDelimiter(0)) |str| { + try w.writeAll(str); + try w.writeByte('\n'); + } - if (d.element(.newlines)) try w.writeByte('\n'); + if (d.element(.newlines)) try w.writeByte('\n'); + } else try w.writeAll("String table not present\n"); } var sections: std.ArrayList(Section) = .empty; -- 2.54.0 From 68680bbd00ae43cd25d8c613d666bd1554d67131 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 19 Sep 2026 17:06:14 -0400 Subject: [PATCH 5/5] link: fix not calling `makeWritable` for dynamic libs during updates --- src/Compilation.zig | 5 +++-- src/link.zig | 22 ++++++++++++++-------- src/main.zig | 10 ++-------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index d94b7146b59afed2774f83d89b63604794a1ddbd..e7da1d5a790a8ac1c6a5e3f8edaab6341b7b77aa 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3528,7 +3528,7 @@ fn emitFromCObject( /// Having the file open for writing is problematic as far as executing the /// binary is concerned. This will remove the write flag, or close the file, /// or whatever is needed so that it can be executed. -/// After this, one must call` makeFileWritable` before calling `update`. +/// After this, one must call `makeFileWritable` before calling `update`. pub fn makeBinFileExecutable(comp: *Compilation) !void { if (!dev.env.supports(.make_executable)) return; const lf = comp.bin_file orelse return; @@ -3537,7 +3537,8 @@ pub fn makeBinFileExecutable(comp: *Compilation) !void { pub fn makeBinFileWritable(comp: *Compilation) !void { const lf = comp.bin_file orelse return; - return lf.makeWritable(); + if (lf.canMakeExecutable()) + try lf.makeWritable(); } const Header = extern struct { diff --git a/src/link.zig b/src/link.zig index 0096023099d00dcdd59e42a4e3e1cae12e777e47..8dc5988ee05b247bede01e8e303d1ae9429a9e2c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -689,18 +689,24 @@ pub const File = struct { return macho.reopenDebugInfo(); } + pub fn canMakeExecutable(base: *File) bool { + const comp = base.comp; + return switch (comp.config.output_mode) { + .Obj => false, + .Lib => switch (comp.config.link_mode) { + .static => false, + .dynamic => true, + }, + .Exe => true, + }; + } + pub fn makeExecutable(base: *File) !void { dev.check(.make_executable); const comp = base.comp; const io = comp.io; - switch (comp.config.output_mode) { - .Obj => return, - .Lib => switch (comp.config.link_mode) { - .static => return, - .dynamic => {}, - }, - .Exe => {}, - } + if (!base.canMakeExecutable()) + return; switch (base.tag) { .lld => assert(base.file == null), .elf => if (base.file) |f| { diff --git a/src/main.zig b/src/main.zig index 41b4e841d36f4bfe598cb23fd300d6ffe248b0f8..5323ea936fa88c1a611ef02d69f5855495e92b73 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4440,12 +4440,8 @@ fn serve( continue; } - if (comp.config.output_mode == .Exe) { - try comp.makeBinFileWritable(); - } - + try comp.makeBinFileWritable(); try comp.update(main_progress_node); - try comp.makeBinFileExecutable(); try serveUpdateResults(&server, comp); }, @@ -4476,9 +4472,7 @@ fn serve( try comp.hotCodeSwap(main_progress_node, pid); try serveUpdateResults(&server, comp); } else { - if (comp.config.output_mode == .Exe) { - try comp.makeBinFileWritable(); - } + try comp.makeBinFileWritable(); try comp.update(main_progress_node); try comp.makeBinFileExecutable(); try serveUpdateResults(&server, comp); -- 2.54.0