From 463fb17320d7320f6f501a266cfd156329ee5cd4 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 25 Aug 2026 13:08:02 +0100 Subject: [PATCH] Elf2: rework archive generation Previously, `Elf2` implemented archives (static libraries) by allowing each member to grow to absorb padding following it. However, I didn't love that we would effectively adding padding bytes to link inputs, and there was no meaningful benefit to allowing reordering of archive members by the `MappedFile` implementation. Ideally, archive members would be densely packed, and their order fixed; and the generated ZCU object can be the last member of the object so that the entire rest of the archive is functionally one long header. Padding bytes in the archive itself (e.g. to align the ZCU object file contents to a block-aligned boundary) should be pulled into the long file names string table ("//"), since that member is generated by us and can be any size. The new `MappedFile` API makes it possible to express this layout fairly easily. The root node of the file has one header node, which contains the archive magic number, the file header for the "//" member, and the actual content of the "//" member; then, all archive *members* are in footer nodes, with the ZCU object being the very last footer (and being block-aligned). Most archive member headers can be written immediately, with the two exceptions being "//" (which updates its `ar_size` field in response to "next_moved" events on the archive header node) and the ZCU object file (which updates its `ar_size` field in response to "resized" events on the ZCU ELF node). Since I was reworking the implementation of archives anyway, I also took the opportunity to add support for long archive member names, and to add graceful error reporting for the archive-specific failure modes (thereby removing most possible calls to `panic` in this linker). Resolves: https://codeberg.org/ziglang/zig/issues/36496 --- src/link/Elf2.zig | 414 +++++++++++++++++++++++++++++++--------------- 1 file changed, 282 insertions(+), 132 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 95c76025570db13d8374c5b18132d0e1ade52aae..4e0bdee194736cdff8716ab0ebdf764637a76b4a 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -24,6 +24,7 @@ base: link.File, options: link.File.OpenOptions, mf: MappedFile, ni: Node.Known, +archive: ?Archive, nodes: std.MultiArrayList(Node), /// Does not contain an item for `SHN_UNDEF`. shdrs: std.ArrayList(Section), @@ -200,19 +201,39 @@ input_prog_node: std.Progress.Node, const Error = link.Error || error{MappedFileIo}; const Node = union(enum) { + /// Only used when emitting a static library. + /// + /// Contains a header node which is an `.archive_header`. + /// + /// Contains the following footer nodes: + /// * One `.archive_input_member` for each external input in the archive + /// * One `.archive_elf_member_header` containing the `ar_hdr` for the ZCU + /// * One `.elf` containing the ZCU's actual ELF object + /// + /// Padding between the headers and footers is absorbed into the "//" member (whose actual + /// content is in the `.archive_header` node). archive, - /// This includes the archive magic and long file member. + /// Only used when emitting a static library. + /// + /// Contains the archive magic (`ARMAG`), as well as the `ar_hdr` and content for the long file + /// name string table member ("//"). archive_header, - /// This is a footer of the `.elf` node, and contains the next archive entry's file header. - archive_elf_footer, + /// Only used when emitting a static library. + /// + /// Contains the `ar_hdr` and content for one non-ZCU archive member (external link input). Also + /// includes the single byte '\n' padding at the end of this archive member, if necessary. + archive_input_member: InputIndex, + /// Only used when emitting a static library. + /// + /// Contains the `ar_hdr` for the `.elf` node. + archive_elf_member_header, + elf, ehdr, shdr, segment: u32, /// The section '.plt' may contain relocations via `elf.plt_first_symbol_reloc`. section: Section.Index, - /// Only valid for static libraries, represents one non-zcu archive member. - input_member: InputIndex, /// May contain relocations. input_section: InputSection.Index, /// Value is the name of a global which has an entry in `elf.copied_globals`, so, a global for @@ -404,6 +425,15 @@ const InputSection = struct { }; }; +const Archive = struct { + ni: MappedFile.Node.Index, + header_ni: MappedFile.Node.Index, + elf_member_header_ni: MappedFile.Node.Index, + + elf_member_too_big: bool, + strtab_member_too_big: bool, +}; + const Section = struct { /// The node corresponding to this section. ni: MappedFile.Node.Index, @@ -2954,13 +2984,13 @@ pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId { const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) { .archive, .archive_header, - .archive_elf_footer, + .archive_input_member, + .archive_elf_member_header, .elf, .ehdr, .shdr, .segment, .section, - .input_member, .input_section, .copied_global, => unreachable, @@ -3364,6 +3394,7 @@ fn create( .data_rel_ro = undefined, .tls = .none, }, + .archive = null, .nodes = .empty, .shdrs = .empty, .phdrs = .empty, @@ -3609,7 +3640,7 @@ fn initHeaders( }; }; - const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_footer + const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_member_header 3 + // `.elf`, `.ehdr`, and `.shdr` nodes (shnum - 1) + // -1 because the SHN_UNDEF shdr does not have a `.section` node (phnum -| 1); // -1 because the GNU_STACK phdr does not have a `.segment` node @@ -3626,11 +3657,14 @@ fn initHeaders( const archive_ni: MappedFile.Node.Index = .root; const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{ - .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr) * 2, - .alignment = .@"2", - .next_moved = true, - .bubbles_moved = false, + // We intentionally do not set `.alignment = .@"2"` here, because the string table data + // in this node does not need to have an aligned length. (This node's offset is aligned + // regardless by virtue of it being a header.) + .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr), + // The archive header uses 'next_moved' events to resize the "//" member, so that it + // absorbs all padding between `archive_header_ni` and the actual object file members. .enable_next_moved = true, + .next_moved = true, }); elf.nodes.appendAssumeCapacity(.archive_header); const archive_header_slice = archive_header_ni.slice(&elf.mf); @@ -3642,23 +3676,47 @@ fn initHeaders( .ar_uid = @splat(' '), .ar_gid = @splat(' '), .ar_mode = @splat(' '), - .ar_size = @splat(' '), + .ar_size = undefined, // populated by `flushNextMoved` for `archive_header_ni` .ar_fmag = std.elf.ARFMAG.*, }; - elf.ni.elf = try archive_ni.addFloatingChild(&elf.mf, gpa, .{ + elf.ni.elf = try archive_ni.addOnlyFooterChild(&elf.mf, gpa, .{ .alignment = node_block_align.max(.@"2"), - .next_moved = true, .bubbles_moved = false, - .enable_next_moved = true, + .resized = true, // ensure that this node's `ar_hdr.ar_size` is updated at least once }); elf.nodes.appendAssumeCapacity(.elf); - _ = try elf.ni.elf.addOnlyFooterChild(&elf.mf, gpa, .{ + const elf_ar_hdr_ni = try archive_ni.addFooterChildBefore(&elf.mf, gpa, .wrap(elf.ni.elf), .{ .alignment = .@"2", .size = @sizeOf(std.elf.ar_hdr), }); - elf.nodes.appendAssumeCapacity(.archive_elf_footer); + elf.nodes.appendAssumeCapacity(.archive_elf_member_header); + + // Must be populated before we call `populateArchiveMemberName` below. + elf.archive = .{ + .ni = archive_ni, + .header_ni = archive_header_ni, + .elf_member_header_ni = elf_ar_hdr_ni, + + .elf_member_too_big = false, + .strtab_member_too_big = false, + }; + + const elf_ar_hdr: *std.elf.ar_hdr = @ptrCast(elf_ar_hdr_ni.slice(&elf.mf)); + elf_ar_hdr.* = .{ + .ar_name = undefined, // populated below + .ar_date = "0 ".*, + .ar_uid = "0 ".*, + .ar_gid = "0 ".*, + .ar_mode = "644 ".*, + .ar_size = undefined, // populated by `flushResized` for the `.elf` node + .ar_fmag = std.elf.ARFMAG.*, + }; + const zcu_member_name = try std.fmt.allocPrint(gpa, "{s}_zcu.o", .{comp.root_name}); + defer gpa.free(zcu_member_name); + // After this call returns, `elf_ar_hdr` is invalidated. + try elf.populateArchiveMemberName(elf_ar_hdr, zcu_member_name); } else { elf.ni.elf = .root; elf.nodes.appendAssumeCapacity(.elf); @@ -4613,12 +4671,12 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index { return switch (elf.getNode(ni)) { .archive, .archive_header, - .archive_elf_footer, + .archive_input_member, + .archive_elf_member_header, .elf, .ehdr, .shdr, .segment, - .input_member, => unreachable, .section => |shndx| shndx, .input_section, @@ -4634,12 +4692,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { return switch (elf.getNode(ni)) { .archive, .archive_header, - .archive_elf_footer, + .archive_input_member, + .archive_elf_member_header, .elf, .ehdr, .shdr, .segment, - .input_member, .copied_global, => unreachable, .section => |shndx| shndx.vaddr(elf), @@ -4653,14 +4711,18 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { } fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { const parent_vaddr = switch (elf.getNode(ni.parent(&elf.mf).unwrap().?)) { - .archive, .archive_header, .archive_elf_footer => unreachable, + .archive, + .archive_header, + .archive_input_member, + .archive_elf_member_header, + => unreachable, .elf => return 0, .ehdr, .shdr => unreachable, .segment => |phndx| switch (elf.phdrSlice()) { inline else => |phdr| elf.targetLoad(&phdr[phndx].vaddr), }, .section => |shndx| if (shndx == elf.shndx.tdata) 0 else shndx.vaddr(elf), - .input_member, .input_section, .copied_global => unreachable, + .input_section, .copied_global => unreachable, inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf), }; const offset, _ = ni.location(&elf.mf).resolve(&elf.mf); @@ -4679,12 +4741,12 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void { const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) { .archive, .archive_header, - .archive_elf_footer, + .archive_input_member, + .archive_elf_member_header, .elf, .ehdr, .shdr, .segment, - .input_member, .copied_global, => unreachable, // cannot contain relocs .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported) @@ -4745,7 +4807,12 @@ fn flushMovedNodeRelocs( // changed, so update the `offset` field of the `ElfN.Rela` entry. reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset); } - reloc.apply(elf); + // This is not just the inverse of the above condition, because if `reloc` is relative + // to the base of this DSO, then `rela_index` is an `R_*_RELATIVE` relocation, but we + // still need to call `SymbolReloc.apply` to update that relocation's addend. + if (elf.ehdrType() != .REL) { + reloc.apply(elf); + } } } @@ -5035,16 +5102,6 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr { } } -fn arHdrPtr(elf: *Elf, ni: MappedFile.Node.Index) *align(2) std.elf.ar_hdr { - assert(elf.ni.elf != .root); - const file_offset = ni.fileLocation(&elf.mf, false).offset; - return @ptrCast(@alignCast(elf.mf.memory_map.memory[@intCast(switch (elf.getNode(ni)) { - else => unreachable, - .archive_header => file_offset + std.elf.ARMAG.len, - .elf, .input_member => file_offset - @sizeOf(std.elf.ar_hdr), - })..][0..@sizeOf(std.elf.ar_hdr)])); -} - const SymPtr = union(std.elf.CLASS) { NONE: noreturn, @"32": *std.elf.Elf32.Sym, @@ -5544,19 +5601,61 @@ fn loadObject( .member = if (member) |m| try gpa.dupe(u8, m) else null, .extra = undefined, }; - if (elf.ni.elf != .root) { - const archive_ni: MappedFile.Node.Index = .root; + if (elf.archive) |*archive| { + // We're creating a static library, so just add this input as an archive member. + assert(member == null); // don't try to put static library members into other static libraries + + const first_member_oni = archive.header_ni.next(&elf.mf); + + if (first_member_oni.unwrap()) |first_member_ni| switch (elf.getNode(first_member_ni)) { + .archive_input_member, .archive_elf_member_header => {}, + .elf => unreachable, // always preceded by `.archive_elf_member_header` + else => unreachable, // never a child of `.archive` + }; + try elf.nodes.ensureUnusedCapacity(gpa, 1); - input.extra = .{ .node = try archive_ni.addFloatingChild(&elf.mf, gpa, .{ - .size = Alignment.@"2".forward(fl.size + @sizeOf(std.elf.ar_hdr)), + const new_member_ni = try archive.ni.addFooterChildBefore(&elf.mf, gpa, first_member_oni, .{ + .size = Alignment.@"2".forward(@sizeOf(std.elf.ar_hdr) + fl.size), .alignment = .@"2", - .next_moved = true, - .bubbles_moved = false, - .enable_next_moved = true, - }) }; - elf.nodes.appendAssumeCapacity(.{ .input_member = input_index }); + }); + elf.nodes.appendAssumeCapacity(.{ .archive_input_member = input_index }); + input.extra = .{ .node = new_member_ni }; elf.input_prog_node.increaseEstimatedTotalItems(1); + // The contents of the input will be written to the file by an idle task (`flushInput`), but + // we do need to write the input's archive member header (`ar_hdr`) now, for two reasons: + // + // * If the input file has a long name, we need to add it to the archive member name string + // table, which must happen deterministically (i.e. not in an idle task). + // + // * `flushInput` needs to know the actual file size (before padding to the alignment). + const member_ar_hdr: *std.elf.ar_hdr = @ptrCast( + new_member_ni.slice(&elf.mf)[0..@sizeOf(std.elf.ar_hdr)], + ); + member_ar_hdr.* = .{ + .ar_name = undefined, // populated below + .ar_date = "0 ".*, + .ar_uid = "0 ".*, + .ar_gid = "0 ".*, + .ar_mode = "644 ".*, + .ar_size = undefined, // populated below + .ar_fmag = std.elf.ARFMAG.*, + }; + + if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{fl.size})) |size_str| { + @memset(member_ar_hdr.ar_size[size_str.len..], ' '); + } else |err| switch (err) { + error.NoSpaceLeft => return diags.failParse( + path, + "file size of {Bi} exceeds maximum size of archive member", + .{fl.size}, + ), + } + + const member_name = std.fs.path.basename(path.sub_path); + // After this call returns, `member_ar_hdr` is invalidated. + try elf.populateArchiveMemberName(member_ar_hdr, member_name); + // Since we are not emitting the archive symbol table (yet?) we do not need to parse // the symbols in this input. return; @@ -5969,6 +6068,46 @@ fn loadObject( }, } } +/// This function may resize the archive header, so therefore invalidates `member_ar_hdr`. +fn populateArchiveMemberName(elf: *Elf, member_ar_hdr: *std.elf.ar_hdr, member_name: []const u8) Error!void { + if (std.mem.print(&member_ar_hdr.ar_name, "{s}/", .{member_name})) |name_str| { + @memset(member_ar_hdr.ar_name[name_str.len..], ' '); + return; + } else |err| switch (err) { + error.NoSpaceLeft => {}, // handled below + } + + const gpa = elf.base.comp.gpa; + const archive_header_ni = elf.archive.?.header_ni; + + // The member's name is too big to put directly in the `ar_name` field, so it needs to go in the + // "long name" string table instead (in the special member named "//"). + + _, const old_archive_header_size = archive_header_ni.location(&elf.mf).resolve(&elf.mf); + + // We're going to add a new string at the end of the table. Update `member_ar_hdr` first, + // because resizing the string table will invalidate it. + const string_table_offset = old_archive_header_size - (std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr)); + if (std.mem.print(&member_ar_hdr.ar_name, "/{d}", .{string_table_offset})) |name_str| { + @memset(member_ar_hdr.ar_name[name_str.len..], ' '); + } else |inner_err| switch (inner_err) { + error.NoSpaceLeft => { + // The string table offset is itself too big to represent. This means the string table's + // *size* is definitely too big to represent (we only get 10 bytes for that whereas we + // get 16 here!), so as long as we still add the string, we're guaranteed to get a link + // error for that reason. Therefore, we can just ignore this error and carry on. + }, + } + + // We set the size of the archive header node exactly, because we want padding bytes to go into + // the root `.archive` node. That way, those bytes could still be used to grow the string table + // if necessary, but they could also be used for new archive members. + try archive_header_ni.resizeLeaf(&elf.mf, gpa, old_archive_header_size + member_name.len + 2); + + const dest_slice = archive_header_ni.slice(&elf.mf)[@intCast(old_archive_header_size)..]; + @memcpy(dest_slice[0 .. dest_slice.len - 2], member_name); + @memcpy(dest_slice[dest_slice.len - 2 ..], "/\n"); // yes, the terminator is weird +} fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void { const comp = elf.base.comp; const gpa = comp.gpa; @@ -7136,12 +7275,12 @@ fn addGotRelocAssumeCapacity( switch (elf.getNode(node)) { .archive, .archive_header, - .archive_elf_footer, + .archive_input_member, + .archive_elf_member_header, .elf, .ehdr, .shdr, .segment, - .input_member, .copied_global, => unreachable, // cannot contain relocs, .section, @@ -7579,6 +7718,17 @@ fn flushInner( diags.addError("failed to apply {d} relocations: misaligned value", .{elf.misaligned_reloc_count}); } + if (elf.archive) |*archive| { + if (archive.elf_member_too_big) diags.addError( + "file size of {Bi} exceeds maximum size of archive member", + .{elf.ni.elf.location(&elf.mf).resolve(&elf.mf)[1]}, + ); + if (archive.strtab_member_too_big) diags.addError( + "archive file name string table exceeds maximum size", + .{}, + ); + } + elf.flushDynamic(); const entry_addr: u64 = entry: { @@ -7610,6 +7760,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool { const comp = elf.base.comp; const diags = &comp.link_diags; + elf.mf.nodes_lock.lock(); + defer elf.mf.nodes_lock.unlock(); + assert(elf.pending_uavs.items.len == 0); for (&elf.lazy.values) |*lazy| { assert(lazy.pending_index == lazy.map.count()); @@ -7763,7 +7916,7 @@ fn idleProgNode( return prog_node.start(name: switch (node) { else => |tag| @tagName(tag), .section => |shndx| shndx.name(elf).slice(elf), - .input_member => |ii| std.fmt.bufPrint(&name, "{f}{f}", .{ + .archive_input_member => |ii| std.fmt.bufPrint(&name, "{f}{f}", .{ ii.path(elf).fmtEscapeString(), fmtMemberString(ii.member(elf)), }) catch &name, @@ -7890,7 +8043,6 @@ fn genLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) Error!void { fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void { const comp = elf.base.comp; const io = comp.io; - const gpa = comp.gpa; const diags = &comp.link_diags; const path = ii.path(elf); const file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| switch (err) { @@ -7898,23 +8050,40 @@ fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void { else => |e| return diags.fail("failed to open input file \"{f}\": {t}", .{ path.fmtEscapeString(), e }), }; defer file.close(io); + + const slice = ii.node(elf).slice(&elf.mf); + + const member_ar_hdr: *const std.elf.ar_hdr = @ptrCast(slice[0..@sizeOf(std.elf.ar_hdr)]); + const input_size: u32 = member_ar_hdr.size() catch |err| switch (err) { + // We wrote the `ar_hdr` ourselves (in `loadObject`), so it is definitely valid. + error.Overflow, error.InvalidCharacter => unreachable, + }; + + switch (slice.len - @sizeOf(std.elf.ar_hdr) - input_size) { + 0 => {}, + 1 => { + // Alignment added one padding byte, which the format requires to have value '\n'. + slice[slice.len - 1] = '\n'; + }, + else => unreachable, // node size should agree with the value we wrote into `ar_hdr.ar_size` + } + var fr = file.reader(io, &.{}); - var nw: MappedFile.Node.Writer = undefined; - ii.node(elf).writer(&elf.mf, gpa, &nw); - defer nw.deinit(); - const size = nw.interface.buffer.len - @sizeOf(std.elf.ar_hdr); - const n_bytes = nw.interface.sendFileAll(&fr, .limited(size)) catch |err| switch (err) { + var w: Io.Writer = .fixed(slice[@sizeOf(std.elf.ar_hdr)..]); + const n_bytes_read = w.sendFileAll(&fr, .limited(input_size)) catch |err| switch (err) { error.ReadFailed => return diags.fail("failed to read input \"{f}{f}\": {t}", .{ path.fmtEscapeString(), fmtMemberString(ii.member(elf)), fr.err orelse (fr.seek_err orelse fr.size_err.?), }), - error.WriteFailed => return nw.err.?, + error.WriteFailed => unreachable, // `.limited(input_size)` prevents us writing too many bytes }; - if (n_bytes + 1 < size) return diags.fail("failed to read input \"{f}{f}\": unexpected eof", .{ - path.fmtEscapeString(), - fmtMemberString(ii.member(elf)), - }); + if (n_bytes_read != input_size) { + return diags.fail("failed to load input \"{f}{f}\": file truncated during compilation", .{ + path.fmtEscapeString(), + fmtMemberString(ii.member(elf)), + }); + } } fn flushInputSection(elf: *Elf, isi: InputSection.Index) Error!void { @@ -7996,12 +8165,18 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void const trace = tracy.trace(@src()); defer trace.end(); - elf.mf.nodes_lock.lock(); - defer elf.mf.nodes_lock.unlock(); - switch (elf.getNode(ni)) { - .archive, .archive_header => unreachable, - .archive_elf_footer, .elf => {}, + .archive => unreachable, + .archive_header => unreachable, + + .archive_input_member, + .archive_elf_member_header, + .elf, + => { + assert(elf.archive != null); + return; + }, + .ehdr, .shdr => elf.flushElfOffset(ni), .segment => |phndx| { elf.flushElfOffset(ni); @@ -8079,7 +8254,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void elf.flushMovedPltSection(.plt_sec, old_addr, addr); } }, - .input_member => {}, .input_section => |isi| { const old_section_addr = isi.ptr(elf).vaddr; const new_section_addr = elf.computeNodeVAddr(ni); @@ -8320,24 +8494,23 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo const trace = tracy.trace(@src()); defer trace.end(); - elf.mf.nodes_lock.lock(); - defer elf.mf.nodes_lock.unlock(); - _, const size = ni.location(&elf.mf).resolve(&elf.mf); switch (elf.getNode(ni)) { - .archive => { - if (ni.last(&elf.mf).unwrap()) |last_ni| { - if (last_ni.prev(&elf.mf).unwrap()) |prev_ni| { - if (prev_ni.hasNextMoved(&elf.mf)) return; - } - const offset, _ = last_ni.location(&elf.mf).resolve(&elf.mf); - _ = std.mem.print(&elf.arHdrPtr(last_ni).ar_size, "{d:<10}", .{ - size - offset, - }) catch @panic("archive member too large"); + .archive, .archive_header => {}, + .archive_input_member => unreachable, + .archive_elf_member_header => unreachable, + .elf => if (elf.archive) |*archive| { + const member_ar_hdr: *std.elf.ar_hdr = @ptrCast( + archive.elf_member_header_ni.slice(&elf.mf), + ); + if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{size})) |size_str| { + @memset(member_ar_hdr.ar_size[size_str.len..], ' '); + archive.elf_member_too_big = false; + } else |err| switch (err) { + error.NoSpaceLeft => archive.elf_member_too_big = true, } }, - .archive_header, .elf => {}, - .ehdr, .archive_elf_footer => unreachable, + .ehdr => unreachable, .shdr => {}, .segment => |phndx| switch (elf.phdrSlice()) { inline else => |phdr| { @@ -8409,7 +8582,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo } }, }, - .input_member, .input_section, .copied_global, .nav, .uav, .lazy_code, .lazy_const_data => {}, + .input_section, .copied_global, .nav, .uav, .lazy_code, .lazy_const_data => {}, } } @@ -8417,12 +8590,11 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error! const trace = tracy.trace(@src()); defer trace.end(); - elf.mf.nodes_lock.lock(); - defer elf.mf.nodes_lock.unlock(); - switch (elf.getNode(ni)) { .archive, - .archive_elf_footer, + .archive_input_member, + .archive_elf_member_header, + .elf, .ehdr, .shdr, .segment, @@ -8434,54 +8606,32 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error! .lazy_code, .lazy_const_data, => unreachable, - .archive_header, .elf, .input_member => |_, tag| { - const member_offset, const update_size = member_offset: { - const offset, _ = ni.location(&elf.mf).resolve(&elf.mf); - break :member_offset switch (tag) { - else => unreachable, - .archive_header => .{ offset + std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr), true }, - .elf, .input_member => .{ offset, !ni.prev(&elf.mf).unwrap().?.hasNextMoved(&elf.mf) }, - }; + + .archive_header => { + const archive = &elf.archive.?; + + // Because we can't just throw padding bytes in the middle of an archive file, we need + // the member name string table (the "//" member) to absorb all the padding bytes + // between it (in the `.archive_header` node) and the first actual member. + const next_member_ni = ni.next(&elf.mf).unwrap() orelse { + // I guess there are no link inputs yet? But there will be eventually! + return; }; - const member_size = if (ni.next(&elf.mf).unwrap()) |next_ni| member_size: { - const next_offset, _ = next_ni.location(&elf.mf).resolve(&elf.mf); - const next_member_size = if (next_ni.next(&elf.mf).unwrap()) |next_next_ni| next_member_size: { - const next_next_offset, _ = next_next_ni.location(&elf.mf).resolve(&elf.mf); - const next_member_end = next_next_offset - @sizeOf(std.elf.ar_hdr); - break :next_member_size next_member_end - next_offset; - } else next_member_size: { - _, const parent_size = ni.parent(&elf.mf).unwrap().?.location(&elf.mf).resolve(&elf.mf); - const next_member_end = parent_size; - break :next_member_size next_member_end - next_offset; - }; - const ar_hdr = elf.arHdrPtr(next_ni); - var name_buf: [16]u8 = undefined; - _ = std.mem.print(&ar_hdr.ar_name, "{s:<16}", .{ - switch (elf.getNode(next_ni)) { - else => unreachable, - .elf => std.mem.print(&name_buf, "{s}_zcu.o/", .{elf.base.comp.root_name}), - .input_member => |ii| std.mem.print(&name_buf, "{s}/", .{ - std.fs.path.basename(ii.path(elf).sub_path), - }), - } catch @panic("TODO: long archive member names"), - }) catch @panic("TODO: long archive member names"); - ar_hdr.ar_date = "0 ".*; - ar_hdr.ar_uid = "0 ".*; - ar_hdr.ar_gid = "0 ".*; - ar_hdr.ar_mode = "644 ".*; - _ = std.mem.print(&ar_hdr.ar_size, "{d:<10}", .{next_member_size}) catch - @panic("archive member too large"); - ar_hdr.ar_fmag = std.elf.ARFMAG.*; - const member_end = next_offset - @sizeOf(std.elf.ar_hdr); - break :member_size member_end - member_offset; - } else member_size: { - _, const parent_size = ni.parent(&elf.mf).unwrap().?.location(&elf.mf).resolve(&elf.mf); - const member_end = parent_size; - break :member_size member_end - member_offset; - }; - if (update_size) _ = std.mem.print(&elf.arHdrPtr(ni).ar_size, "{d:<10}", .{ - member_size, - }) catch @panic("archive member too large"); + const next_member_offset: u64, _ = next_member_ni.location(&elf.mf).resolve(&elf.mf); + const strtab_member_offset = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr); + assert(Alignment.@"2".check(next_member_offset)); + assert(Alignment.@"2".check(strtab_member_offset)); + const strtab_size = next_member_offset - strtab_member_offset; + + const member_ar_hdr: *std.elf.ar_hdr = @ptrCast( + archive.header_ni.slice(&elf.mf)[std.elf.ARMAG.len..][0..@sizeOf(std.elf.ar_hdr)], + ); + if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{strtab_size})) |size_str| { + @memset(member_ar_hdr.ar_size[size_str.len..], ' '); + archive.strtab_member_too_big = false; + } else |err| switch (err) { + error.NoSpaceLeft => archive.strtab_member_too_big = true, + } }, } } -- 2.54.0