authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-25 13:08:02+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-29 13:33:14+01:00
log463fb17320d7320f6f501a266cfd156329ee5cd4
treeb51ef6756603f0b1d8f013e02f837ed1331d867d
parent13f71bb636b3912952297c4b615edcc64c73a918
signaturelock-open Commit is signed but in an unrecognized format.

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

1 files changed, 282 insertions(+), 132 deletions(-)

src/link/Elf2.zig+282-132
......@@ -24,6 +24,7 @@ base: link.File,
2424options: link.File.OpenOptions,
2525mf: MappedFile,
2626ni: Node.Known,
27archive: ?Archive,
2728nodes: std.MultiArrayList(Node),
2829/// Does not contain an item for `SHN_UNDEF`.
2930shdrs: std.ArrayList(Section),
......@@ -200,19 +201,39 @@ input_prog_node: std.Progress.Node,
200201const Error = link.Error || error{MappedFileIo};
201202
202203const Node = union(enum) {
204 /// Only used when emitting a static library.
205 ///
206 /// Contains a header node which is an `.archive_header`.
207 ///
208 /// Contains the following footer nodes:
209 /// * One `.archive_input_member` for each external input in the archive
210 /// * One `.archive_elf_member_header` containing the `ar_hdr` for the ZCU
211 /// * One `.elf` containing the ZCU's actual ELF object
212 ///
213 /// Padding between the headers and footers is absorbed into the "//" member (whose actual
214 /// content is in the `.archive_header` node).
203215 archive,
204 /// This includes the archive magic and long file member.
216 /// Only used when emitting a static library.
217 ///
218 /// Contains the archive magic (`ARMAG`), as well as the `ar_hdr` and content for the long file
219 /// name string table member ("//").
205220 archive_header,
206 /// This is a footer of the `.elf` node, and contains the next archive entry's file header.
207 archive_elf_footer,
221 /// Only used when emitting a static library.
222 ///
223 /// Contains the `ar_hdr` and content for one non-ZCU archive member (external link input). Also
224 /// includes the single byte '\n' padding at the end of this archive member, if necessary.
225 archive_input_member: InputIndex,
226 /// Only used when emitting a static library.
227 ///
228 /// Contains the `ar_hdr` for the `.elf` node.
229 archive_elf_member_header,
230
208231 elf,
209232 ehdr,
210233 shdr,
211234 segment: u32,
212235 /// The section '.plt' may contain relocations via `elf.plt_first_symbol_reloc`.
213236 section: Section.Index,
214 /// Only valid for static libraries, represents one non-zcu archive member.
215 input_member: InputIndex,
216237 /// May contain relocations.
217238 input_section: InputSection.Index,
218239 /// 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 {
404425 };
405426};
406427
428const Archive = struct {
429 ni: MappedFile.Node.Index,
430 header_ni: MappedFile.Node.Index,
431 elf_member_header_ni: MappedFile.Node.Index,
432
433 elf_member_too_big: bool,
434 strtab_member_too_big: bool,
435};
436
407437const Section = struct {
408438 /// The node corresponding to this section.
409439 ni: MappedFile.Node.Index,
......@@ -2954,13 +2984,13 @@ pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {
29542984 const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) {
29552985 .archive,
29562986 .archive_header,
2957 .archive_elf_footer,
2987 .archive_input_member,
2988 .archive_elf_member_header,
29582989 .elf,
29592990 .ehdr,
29602991 .shdr,
29612992 .segment,
29622993 .section,
2963 .input_member,
29642994 .input_section,
29652995 .copied_global,
29662996 => unreachable,
......@@ -3364,6 +3394,7 @@ fn create(
33643394 .data_rel_ro = undefined,
33653395 .tls = .none,
33663396 },
3397 .archive = null,
33673398 .nodes = .empty,
33683399 .shdrs = .empty,
33693400 .phdrs = .empty,
......@@ -3609,7 +3640,7 @@ fn initHeaders(
36093640 };
36103641 };
36113642
3612 const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_footer
3643 const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_member_header
36133644 3 + // `.elf`, `.ehdr`, and `.shdr` nodes
36143645 (shnum - 1) + // -1 because the SHN_UNDEF shdr does not have a `.section` node
36153646 (phnum -| 1); // -1 because the GNU_STACK phdr does not have a `.segment` node
......@@ -3626,11 +3657,14 @@ fn initHeaders(
36263657 const archive_ni: MappedFile.Node.Index = .root;
36273658
36283659 const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{
3629 .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr) * 2,
3630 .alignment = .@"2",
3631 .next_moved = true,
3632 .bubbles_moved = false,
3660 // We intentionally do not set `.alignment = .@"2"` here, because the string table data
3661 // in this node does not need to have an aligned length. (This node's offset is aligned
3662 // regardless by virtue of it being a header.)
3663 .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr),
3664 // The archive header uses 'next_moved' events to resize the "//" member, so that it
3665 // absorbs all padding between `archive_header_ni` and the actual object file members.
36333666 .enable_next_moved = true,
3667 .next_moved = true,
36343668 });
36353669 elf.nodes.appendAssumeCapacity(.archive_header);
36363670 const archive_header_slice = archive_header_ni.slice(&elf.mf);
......@@ -3642,23 +3676,47 @@ fn initHeaders(
36423676 .ar_uid = @splat(' '),
36433677 .ar_gid = @splat(' '),
36443678 .ar_mode = @splat(' '),
3645 .ar_size = @splat(' '),
3679 .ar_size = undefined, // populated by `flushNextMoved` for `archive_header_ni`
36463680 .ar_fmag = std.elf.ARFMAG.*,
36473681 };
36483682
3649 elf.ni.elf = try archive_ni.addFloatingChild(&elf.mf, gpa, .{
3683 elf.ni.elf = try archive_ni.addOnlyFooterChild(&elf.mf, gpa, .{
36503684 .alignment = node_block_align.max(.@"2"),
3651 .next_moved = true,
36523685 .bubbles_moved = false,
3653 .enable_next_moved = true,
3686 .resized = true, // ensure that this node's `ar_hdr.ar_size` is updated at least once
36543687 });
36553688 elf.nodes.appendAssumeCapacity(.elf);
36563689
3657 _ = try elf.ni.elf.addOnlyFooterChild(&elf.mf, gpa, .{
3690 const elf_ar_hdr_ni = try archive_ni.addFooterChildBefore(&elf.mf, gpa, .wrap(elf.ni.elf), .{
36583691 .alignment = .@"2",
36593692 .size = @sizeOf(std.elf.ar_hdr),
36603693 });
3661 elf.nodes.appendAssumeCapacity(.archive_elf_footer);
3694 elf.nodes.appendAssumeCapacity(.archive_elf_member_header);
3695
3696 // Must be populated before we call `populateArchiveMemberName` below.
3697 elf.archive = .{
3698 .ni = archive_ni,
3699 .header_ni = archive_header_ni,
3700 .elf_member_header_ni = elf_ar_hdr_ni,
3701
3702 .elf_member_too_big = false,
3703 .strtab_member_too_big = false,
3704 };
3705
3706 const elf_ar_hdr: *std.elf.ar_hdr = @ptrCast(elf_ar_hdr_ni.slice(&elf.mf));
3707 elf_ar_hdr.* = .{
3708 .ar_name = undefined, // populated below
3709 .ar_date = "0 ".*,
3710 .ar_uid = "0 ".*,
3711 .ar_gid = "0 ".*,
3712 .ar_mode = "644 ".*,
3713 .ar_size = undefined, // populated by `flushResized` for the `.elf` node
3714 .ar_fmag = std.elf.ARFMAG.*,
3715 };
3716 const zcu_member_name = try std.fmt.allocPrint(gpa, "{s}_zcu.o", .{comp.root_name});
3717 defer gpa.free(zcu_member_name);
3718 // After this call returns, `elf_ar_hdr` is invalidated.
3719 try elf.populateArchiveMemberName(elf_ar_hdr, zcu_member_name);
36623720 } else {
36633721 elf.ni.elf = .root;
36643722 elf.nodes.appendAssumeCapacity(.elf);
......@@ -4613,12 +4671,12 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {
46134671 return switch (elf.getNode(ni)) {
46144672 .archive,
46154673 .archive_header,
4616 .archive_elf_footer,
4674 .archive_input_member,
4675 .archive_elf_member_header,
46174676 .elf,
46184677 .ehdr,
46194678 .shdr,
46204679 .segment,
4621 .input_member,
46224680 => unreachable,
46234681 .section => |shndx| shndx,
46244682 .input_section,
......@@ -4634,12 +4692,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
46344692 return switch (elf.getNode(ni)) {
46354693 .archive,
46364694 .archive_header,
4637 .archive_elf_footer,
4695 .archive_input_member,
4696 .archive_elf_member_header,
46384697 .elf,
46394698 .ehdr,
46404699 .shdr,
46414700 .segment,
4642 .input_member,
46434701 .copied_global,
46444702 => unreachable,
46454703 .section => |shndx| shndx.vaddr(elf),
......@@ -4653,14 +4711,18 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
46534711}
46544712fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
46554713 const parent_vaddr = switch (elf.getNode(ni.parent(&elf.mf).unwrap().?)) {
4656 .archive, .archive_header, .archive_elf_footer => unreachable,
4714 .archive,
4715 .archive_header,
4716 .archive_input_member,
4717 .archive_elf_member_header,
4718 => unreachable,
46574719 .elf => return 0,
46584720 .ehdr, .shdr => unreachable,
46594721 .segment => |phndx| switch (elf.phdrSlice()) {
46604722 inline else => |phdr| elf.targetLoad(&phdr[phndx].vaddr),
46614723 },
46624724 .section => |shndx| if (shndx == elf.shndx.tdata) 0 else shndx.vaddr(elf),
4663 .input_member, .input_section, .copied_global => unreachable,
4725 .input_section, .copied_global => unreachable,
46644726 inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf),
46654727 };
46664728 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);
......@@ -4679,12 +4741,12 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void {
46794741 const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) {
46804742 .archive,
46814743 .archive_header,
4682 .archive_elf_footer,
4744 .archive_input_member,
4745 .archive_elf_member_header,
46834746 .elf,
46844747 .ehdr,
46854748 .shdr,
46864749 .segment,
4687 .input_member,
46884750 .copied_global,
46894751 => unreachable, // cannot contain relocs
46904752 .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported)
......@@ -4745,7 +4807,12 @@ fn flushMovedNodeRelocs(
47454807 // changed, so update the `offset` field of the `ElfN.Rela` entry.
47464808 reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset);
47474809 }
4748 reloc.apply(elf);
4810 // This is not just the inverse of the above condition, because if `reloc` is relative
4811 // to the base of this DSO, then `rela_index` is an `R_*_RELATIVE` relocation, but we
4812 // still need to call `SymbolReloc.apply` to update that relocation's addend.
4813 if (elf.ehdrType() != .REL) {
4814 reloc.apply(elf);
4815 }
47494816 }
47504817 }
47514818
......@@ -5035,16 +5102,6 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr {
50355102 }
50365103}
50375104
5038fn arHdrPtr(elf: *Elf, ni: MappedFile.Node.Index) *align(2) std.elf.ar_hdr {
5039 assert(elf.ni.elf != .root);
5040 const file_offset = ni.fileLocation(&elf.mf, false).offset;
5041 return @ptrCast(@alignCast(elf.mf.memory_map.memory[@intCast(switch (elf.getNode(ni)) {
5042 else => unreachable,
5043 .archive_header => file_offset + std.elf.ARMAG.len,
5044 .elf, .input_member => file_offset - @sizeOf(std.elf.ar_hdr),
5045 })..][0..@sizeOf(std.elf.ar_hdr)]));
5046}
5047
50485105const SymPtr = union(std.elf.CLASS) {
50495106 NONE: noreturn,
50505107 @"32": *std.elf.Elf32.Sym,
......@@ -5544,19 +5601,61 @@ fn loadObject(
55445601 .member = if (member) |m| try gpa.dupe(u8, m) else null,
55455602 .extra = undefined,
55465603 };
5547 if (elf.ni.elf != .root) {
5548 const archive_ni: MappedFile.Node.Index = .root;
5604 if (elf.archive) |*archive| {
5605 // We're creating a static library, so just add this input as an archive member.
5606 assert(member == null); // don't try to put static library members into other static libraries
5607
5608 const first_member_oni = archive.header_ni.next(&elf.mf);
5609
5610 if (first_member_oni.unwrap()) |first_member_ni| switch (elf.getNode(first_member_ni)) {
5611 .archive_input_member, .archive_elf_member_header => {},
5612 .elf => unreachable, // always preceded by `.archive_elf_member_header`
5613 else => unreachable, // never a child of `.archive`
5614 };
5615
55495616 try elf.nodes.ensureUnusedCapacity(gpa, 1);
5550 input.extra = .{ .node = try archive_ni.addFloatingChild(&elf.mf, gpa, .{
5551 .size = Alignment.@"2".forward(fl.size + @sizeOf(std.elf.ar_hdr)),
5617 const new_member_ni = try archive.ni.addFooterChildBefore(&elf.mf, gpa, first_member_oni, .{
5618 .size = Alignment.@"2".forward(@sizeOf(std.elf.ar_hdr) + fl.size),
55525619 .alignment = .@"2",
5553 .next_moved = true,
5554 .bubbles_moved = false,
5555 .enable_next_moved = true,
5556 }) };
5557 elf.nodes.appendAssumeCapacity(.{ .input_member = input_index });
5620 });
5621 elf.nodes.appendAssumeCapacity(.{ .archive_input_member = input_index });
5622 input.extra = .{ .node = new_member_ni };
55585623 elf.input_prog_node.increaseEstimatedTotalItems(1);
55595624
5625 // The contents of the input will be written to the file by an idle task (`flushInput`), but
5626 // we do need to write the input's archive member header (`ar_hdr`) now, for two reasons:
5627 //
5628 // * If the input file has a long name, we need to add it to the archive member name string
5629 // table, which must happen deterministically (i.e. not in an idle task).
5630 //
5631 // * `flushInput` needs to know the actual file size (before padding to the alignment).
5632 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
5633 new_member_ni.slice(&elf.mf)[0..@sizeOf(std.elf.ar_hdr)],
5634 );
5635 member_ar_hdr.* = .{
5636 .ar_name = undefined, // populated below
5637 .ar_date = "0 ".*,
5638 .ar_uid = "0 ".*,
5639 .ar_gid = "0 ".*,
5640 .ar_mode = "644 ".*,
5641 .ar_size = undefined, // populated below
5642 .ar_fmag = std.elf.ARFMAG.*,
5643 };
5644
5645 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{fl.size})) |size_str| {
5646 @memset(member_ar_hdr.ar_size[size_str.len..], ' ');
5647 } else |err| switch (err) {
5648 error.NoSpaceLeft => return diags.failParse(
5649 path,
5650 "file size of {Bi} exceeds maximum size of archive member",
5651 .{fl.size},
5652 ),
5653 }
5654
5655 const member_name = std.fs.path.basename(path.sub_path);
5656 // After this call returns, `member_ar_hdr` is invalidated.
5657 try elf.populateArchiveMemberName(member_ar_hdr, member_name);
5658
55605659 // Since we are not emitting the archive symbol table (yet?) we do not need to parse
55615660 // the symbols in this input.
55625661 return;
......@@ -5969,6 +6068,46 @@ fn loadObject(
59696068 },
59706069 }
59716070}
6071/// This function may resize the archive header, so therefore invalidates `member_ar_hdr`.
6072fn populateArchiveMemberName(elf: *Elf, member_ar_hdr: *std.elf.ar_hdr, member_name: []const u8) Error!void {
6073 if (std.mem.print(&member_ar_hdr.ar_name, "{s}/", .{member_name})) |name_str| {
6074 @memset(member_ar_hdr.ar_name[name_str.len..], ' ');
6075 return;
6076 } else |err| switch (err) {
6077 error.NoSpaceLeft => {}, // handled below
6078 }
6079
6080 const gpa = elf.base.comp.gpa;
6081 const archive_header_ni = elf.archive.?.header_ni;
6082
6083 // The member's name is too big to put directly in the `ar_name` field, so it needs to go in the
6084 // "long name" string table instead (in the special member named "//").
6085
6086 _, const old_archive_header_size = archive_header_ni.location(&elf.mf).resolve(&elf.mf);
6087
6088 // We're going to add a new string at the end of the table. Update `member_ar_hdr` first,
6089 // because resizing the string table will invalidate it.
6090 const string_table_offset = old_archive_header_size - (std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr));
6091 if (std.mem.print(&member_ar_hdr.ar_name, "/{d}", .{string_table_offset})) |name_str| {
6092 @memset(member_ar_hdr.ar_name[name_str.len..], ' ');
6093 } else |inner_err| switch (inner_err) {
6094 error.NoSpaceLeft => {
6095 // The string table offset is itself too big to represent. This means the string table's
6096 // *size* is definitely too big to represent (we only get 10 bytes for that whereas we
6097 // get 16 here!), so as long as we still add the string, we're guaranteed to get a link
6098 // error for that reason. Therefore, we can just ignore this error and carry on.
6099 },
6100 }
6101
6102 // We set the size of the archive header node exactly, because we want padding bytes to go into
6103 // the root `.archive` node. That way, those bytes could still be used to grow the string table
6104 // if necessary, but they could also be used for new archive members.
6105 try archive_header_ni.resizeLeaf(&elf.mf, gpa, old_archive_header_size + member_name.len + 2);
6106
6107 const dest_slice = archive_header_ni.slice(&elf.mf)[@intCast(old_archive_header_size)..];
6108 @memcpy(dest_slice[0 .. dest_slice.len - 2], member_name);
6109 @memcpy(dest_slice[dest_slice.len - 2 ..], "/\n"); // yes, the terminator is weird
6110}
59726111fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {
59736112 const comp = elf.base.comp;
59746113 const gpa = comp.gpa;
......@@ -7136,12 +7275,12 @@ fn addGotRelocAssumeCapacity(
71367275 switch (elf.getNode(node)) {
71377276 .archive,
71387277 .archive_header,
7139 .archive_elf_footer,
7278 .archive_input_member,
7279 .archive_elf_member_header,
71407280 .elf,
71417281 .ehdr,
71427282 .shdr,
71437283 .segment,
7144 .input_member,
71457284 .copied_global,
71467285 => unreachable, // cannot contain relocs,
71477286 .section,
......@@ -7579,6 +7718,17 @@ fn flushInner(
75797718 diags.addError("failed to apply {d} relocations: misaligned value", .{elf.misaligned_reloc_count});
75807719 }
75817720
7721 if (elf.archive) |*archive| {
7722 if (archive.elf_member_too_big) diags.addError(
7723 "file size of {Bi} exceeds maximum size of archive member",
7724 .{elf.ni.elf.location(&elf.mf).resolve(&elf.mf)[1]},
7725 );
7726 if (archive.strtab_member_too_big) diags.addError(
7727 "archive file name string table exceeds maximum size",
7728 .{},
7729 );
7730 }
7731
75827732 elf.flushDynamic();
75837733
75847734 const entry_addr: u64 = entry: {
......@@ -7610,6 +7760,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool {
76107760 const comp = elf.base.comp;
76117761 const diags = &comp.link_diags;
76127762
7763 elf.mf.nodes_lock.lock();
7764 defer elf.mf.nodes_lock.unlock();
7765
76137766 assert(elf.pending_uavs.items.len == 0);
76147767 for (&elf.lazy.values) |*lazy| {
76157768 assert(lazy.pending_index == lazy.map.count());
......@@ -7763,7 +7916,7 @@ fn idleProgNode(
77637916 return prog_node.start(name: switch (node) {
77647917 else => |tag| @tagName(tag),
77657918 .section => |shndx| shndx.name(elf).slice(elf),
7766 .input_member => |ii| std.fmt.bufPrint(&name, "{f}{f}", .{
7919 .archive_input_member => |ii| std.fmt.bufPrint(&name, "{f}{f}", .{
77677920 ii.path(elf).fmtEscapeString(),
77687921 fmtMemberString(ii.member(elf)),
77697922 }) catch &name,
......@@ -7890,7 +8043,6 @@ fn genLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) Error!void {
78908043fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {
78918044 const comp = elf.base.comp;
78928045 const io = comp.io;
7893 const gpa = comp.gpa;
78948046 const diags = &comp.link_diags;
78958047 const path = ii.path(elf);
78968048 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 {
78988050 else => |e| return diags.fail("failed to open input file \"{f}\": {t}", .{ path.fmtEscapeString(), e }),
78998051 };
79008052 defer file.close(io);
8053
8054 const slice = ii.node(elf).slice(&elf.mf);
8055
8056 const member_ar_hdr: *const std.elf.ar_hdr = @ptrCast(slice[0..@sizeOf(std.elf.ar_hdr)]);
8057 const input_size: u32 = member_ar_hdr.size() catch |err| switch (err) {
8058 // We wrote the `ar_hdr` ourselves (in `loadObject`), so it is definitely valid.
8059 error.Overflow, error.InvalidCharacter => unreachable,
8060 };
8061
8062 switch (slice.len - @sizeOf(std.elf.ar_hdr) - input_size) {
8063 0 => {},
8064 1 => {
8065 // Alignment added one padding byte, which the format requires to have value '\n'.
8066 slice[slice.len - 1] = '\n';
8067 },
8068 else => unreachable, // node size should agree with the value we wrote into `ar_hdr.ar_size`
8069 }
8070
79018071 var fr = file.reader(io, &.{});
7902 var nw: MappedFile.Node.Writer = undefined;
7903 ii.node(elf).writer(&elf.mf, gpa, &nw);
7904 defer nw.deinit();
7905 const size = nw.interface.buffer.len - @sizeOf(std.elf.ar_hdr);
7906 const n_bytes = nw.interface.sendFileAll(&fr, .limited(size)) catch |err| switch (err) {
8072 var w: Io.Writer = .fixed(slice[@sizeOf(std.elf.ar_hdr)..]);
8073 const n_bytes_read = w.sendFileAll(&fr, .limited(input_size)) catch |err| switch (err) {
79078074 error.ReadFailed => return diags.fail("failed to read input \"{f}{f}\": {t}", .{
79088075 path.fmtEscapeString(),
79098076 fmtMemberString(ii.member(elf)),
79108077 fr.err orelse (fr.seek_err orelse fr.size_err.?),
79118078 }),
7912 error.WriteFailed => return nw.err.?,
8079 error.WriteFailed => unreachable, // `.limited(input_size)` prevents us writing too many bytes
79138080 };
7914 if (n_bytes + 1 < size) return diags.fail("failed to read input \"{f}{f}\": unexpected eof", .{
7915 path.fmtEscapeString(),
7916 fmtMemberString(ii.member(elf)),
7917 });
8081 if (n_bytes_read != input_size) {
8082 return diags.fail("failed to load input \"{f}{f}\": file truncated during compilation", .{
8083 path.fmtEscapeString(),
8084 fmtMemberString(ii.member(elf)),
8085 });
8086 }
79188087}
79198088
79208089fn 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
79968165 const trace = tracy.trace(@src());
79978166 defer trace.end();
79988167
7999 elf.mf.nodes_lock.lock();
8000 defer elf.mf.nodes_lock.unlock();
8001
80028168 switch (elf.getNode(ni)) {
8003 .archive, .archive_header => unreachable,
8004 .archive_elf_footer, .elf => {},
8169 .archive => unreachable,
8170 .archive_header => unreachable,
8171
8172 .archive_input_member,
8173 .archive_elf_member_header,
8174 .elf,
8175 => {
8176 assert(elf.archive != null);
8177 return;
8178 },
8179
80058180 .ehdr, .shdr => elf.flushElfOffset(ni),
80068181 .segment => |phndx| {
80078182 elf.flushElfOffset(ni);
......@@ -8079,7 +8254,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
80798254 elf.flushMovedPltSection(.plt_sec, old_addr, addr);
80808255 }
80818256 },
8082 .input_member => {},
80838257 .input_section => |isi| {
80848258 const old_section_addr = isi.ptr(elf).vaddr;
80858259 const new_section_addr = elf.computeNodeVAddr(ni);
......@@ -8320,24 +8494,23 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
83208494 const trace = tracy.trace(@src());
83218495 defer trace.end();
83228496
8323 elf.mf.nodes_lock.lock();
8324 defer elf.mf.nodes_lock.unlock();
8325
83268497 _, const size = ni.location(&elf.mf).resolve(&elf.mf);
83278498 switch (elf.getNode(ni)) {
8328 .archive => {
8329 if (ni.last(&elf.mf).unwrap()) |last_ni| {
8330 if (last_ni.prev(&elf.mf).unwrap()) |prev_ni| {
8331 if (prev_ni.hasNextMoved(&elf.mf)) return;
8332 }
8333 const offset, _ = last_ni.location(&elf.mf).resolve(&elf.mf);
8334 _ = std.mem.print(&elf.arHdrPtr(last_ni).ar_size, "{d:<10}", .{
8335 size - offset,
8336 }) catch @panic("archive member too large");
8499 .archive, .archive_header => {},
8500 .archive_input_member => unreachable,
8501 .archive_elf_member_header => unreachable,
8502 .elf => if (elf.archive) |*archive| {
8503 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
8504 archive.elf_member_header_ni.slice(&elf.mf),
8505 );
8506 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{size})) |size_str| {
8507 @memset(member_ar_hdr.ar_size[size_str.len..], ' ');
8508 archive.elf_member_too_big = false;
8509 } else |err| switch (err) {
8510 error.NoSpaceLeft => archive.elf_member_too_big = true,
83378511 }
83388512 },
8339 .archive_header, .elf => {},
8340 .ehdr, .archive_elf_footer => unreachable,
8513 .ehdr => unreachable,
83418514 .shdr => {},
83428515 .segment => |phndx| switch (elf.phdrSlice()) {
83438516 inline else => |phdr| {
......@@ -8409,7 +8582,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
84098582 }
84108583 },
84118584 },
8412 .input_member, .input_section, .copied_global, .nav, .uav, .lazy_code, .lazy_const_data => {},
8585 .input_section, .copied_global, .nav, .uav, .lazy_code, .lazy_const_data => {},
84138586 }
84148587}
84158588
......@@ -8417,12 +8590,11 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!
84178590 const trace = tracy.trace(@src());
84188591 defer trace.end();
84198592
8420 elf.mf.nodes_lock.lock();
8421 defer elf.mf.nodes_lock.unlock();
8422
84238593 switch (elf.getNode(ni)) {
84248594 .archive,
8425 .archive_elf_footer,
8595 .archive_input_member,
8596 .archive_elf_member_header,
8597 .elf,
84268598 .ehdr,
84278599 .shdr,
84288600 .segment,
......@@ -8434,54 +8606,32 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!
84348606 .lazy_code,
84358607 .lazy_const_data,
84368608 => unreachable,
8437 .archive_header, .elf, .input_member => |_, tag| {
8438 const member_offset, const update_size = member_offset: {
8439 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);
8440 break :member_offset switch (tag) {
8441 else => unreachable,
8442 .archive_header => .{ offset + std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr), true },
8443 .elf, .input_member => .{ offset, !ni.prev(&elf.mf).unwrap().?.hasNextMoved(&elf.mf) },
8444 };
8445 };
8446 const member_size = if (ni.next(&elf.mf).unwrap()) |next_ni| member_size: {
8447 const next_offset, _ = next_ni.location(&elf.mf).resolve(&elf.mf);
8448 const next_member_size = if (next_ni.next(&elf.mf).unwrap()) |next_next_ni| next_member_size: {
8449 const next_next_offset, _ = next_next_ni.location(&elf.mf).resolve(&elf.mf);
8450 const next_member_end = next_next_offset - @sizeOf(std.elf.ar_hdr);
8451 break :next_member_size next_member_end - next_offset;
8452 } else next_member_size: {
8453 _, const parent_size = ni.parent(&elf.mf).unwrap().?.location(&elf.mf).resolve(&elf.mf);
8454 const next_member_end = parent_size;
8455 break :next_member_size next_member_end - next_offset;
8456 };
8457 const ar_hdr = elf.arHdrPtr(next_ni);
8458 var name_buf: [16]u8 = undefined;
8459 _ = std.mem.print(&ar_hdr.ar_name, "{s:<16}", .{
8460 switch (elf.getNode(next_ni)) {
8461 else => unreachable,
8462 .elf => std.mem.print(&name_buf, "{s}_zcu.o/", .{elf.base.comp.root_name}),
8463 .input_member => |ii| std.mem.print(&name_buf, "{s}/", .{
8464 std.fs.path.basename(ii.path(elf).sub_path),
8465 }),
8466 } catch @panic("TODO: long archive member names"),
8467 }) catch @panic("TODO: long archive member names");
8468 ar_hdr.ar_date = "0 ".*;
8469 ar_hdr.ar_uid = "0 ".*;
8470 ar_hdr.ar_gid = "0 ".*;
8471 ar_hdr.ar_mode = "644 ".*;
8472 _ = std.mem.print(&ar_hdr.ar_size, "{d:<10}", .{next_member_size}) catch
8473 @panic("archive member too large");
8474 ar_hdr.ar_fmag = std.elf.ARFMAG.*;
8475 const member_end = next_offset - @sizeOf(std.elf.ar_hdr);
8476 break :member_size member_end - member_offset;
8477 } else member_size: {
8478 _, const parent_size = ni.parent(&elf.mf).unwrap().?.location(&elf.mf).resolve(&elf.mf);
8479 const member_end = parent_size;
8480 break :member_size member_end - member_offset;
8609
8610 .archive_header => {
8611 const archive = &elf.archive.?;
8612
8613 // Because we can't just throw padding bytes in the middle of an archive file, we need
8614 // the member name string table (the "//" member) to absorb all the padding bytes
8615 // between it (in the `.archive_header` node) and the first actual member.
8616 const next_member_ni = ni.next(&elf.mf).unwrap() orelse {
8617 // I guess there are no link inputs yet? But there will be eventually!
8618 return;
84818619 };
8482 if (update_size) _ = std.mem.print(&elf.arHdrPtr(ni).ar_size, "{d:<10}", .{
8483 member_size,
8484 }) catch @panic("archive member too large");
8620 const next_member_offset: u64, _ = next_member_ni.location(&elf.mf).resolve(&elf.mf);
8621 const strtab_member_offset = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr);
8622 assert(Alignment.@"2".check(next_member_offset));
8623 assert(Alignment.@"2".check(strtab_member_offset));
8624 const strtab_size = next_member_offset - strtab_member_offset;
8625
8626 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
8627 archive.header_ni.slice(&elf.mf)[std.elf.ARMAG.len..][0..@sizeOf(std.elf.ar_hdr)],
8628 );
8629 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{strtab_size})) |size_str| {
8630 @memset(member_ar_hdr.ar_size[size_str.len..], ' ');
8631 archive.strtab_member_too_big = false;
8632 } else |err| switch (err) {
8633 error.NoSpaceLeft => archive.strtab_member_too_big = true,
8634 }
84858635 },
84868636 }
84878637}