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,...@@ -24,6 +24,7 @@ base: link.File,
24options: link.File.OpenOptions,24options: link.File.OpenOptions,
25mf: MappedFile,25mf: MappedFile,
26ni: Node.Known,26ni: Node.Known,
27archive: ?Archive,
27nodes: std.MultiArrayList(Node),28nodes: std.MultiArrayList(Node),
28/// Does not contain an item for `SHN_UNDEF`.29/// Does not contain an item for `SHN_UNDEF`.
29shdrs: std.ArrayList(Section),30shdrs: std.ArrayList(Section),
...@@ -200,19 +201,39 @@ input_prog_node: std.Progress.Node,...@@ -200,19 +201,39 @@ input_prog_node: std.Progress.Node,
200const Error = link.Error || error{MappedFileIo};201const Error = link.Error || error{MappedFileIo};
201202
202const Node = union(enum) {203const 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).
203 archive,215 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 ("//").
205 archive_header,220 archive_header,
206 /// This is a footer of the `.elf` node, and contains the next archive entry's file header.221 /// Only used when emitting a static library.
207 archive_elf_footer,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
208 elf,231 elf,
209 ehdr,232 ehdr,
210 shdr,233 shdr,
211 segment: u32,234 segment: u32,
212 /// The section '.plt' may contain relocations via `elf.plt_first_symbol_reloc`.235 /// The section '.plt' may contain relocations via `elf.plt_first_symbol_reloc`.
213 section: Section.Index,236 section: Section.Index,
214 /// Only valid for static libraries, represents one non-zcu archive member.
215 input_member: InputIndex,
216 /// May contain relocations.237 /// May contain relocations.
217 input_section: InputSection.Index,238 input_section: InputSection.Index,
218 /// Value is the name of a global which has an entry in `elf.copied_globals`, so, a global for239 /// 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 {...@@ -404,6 +425,15 @@ const InputSection = struct {
404 };425 };
405};426};
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
407const Section = struct {437const Section = struct {
408 /// The node corresponding to this section.438 /// The node corresponding to this section.
409 ni: MappedFile.Node.Index,439 ni: MappedFile.Node.Index,
...@@ -2954,13 +2984,13 @@ pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {...@@ -2954,13 +2984,13 @@ pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {
2954 const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) {2984 const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) {
2955 .archive,2985 .archive,
2956 .archive_header,2986 .archive_header,
2957 .archive_elf_footer,2987 .archive_input_member,
2988 .archive_elf_member_header,
2958 .elf,2989 .elf,
2959 .ehdr,2990 .ehdr,
2960 .shdr,2991 .shdr,
2961 .segment,2992 .segment,
2962 .section,2993 .section,
2963 .input_member,
2964 .input_section,2994 .input_section,
2965 .copied_global,2995 .copied_global,
2966 => unreachable,2996 => unreachable,
...@@ -3364,6 +3394,7 @@ fn create(...@@ -3364,6 +3394,7 @@ fn create(
3364 .data_rel_ro = undefined,3394 .data_rel_ro = undefined,
3365 .tls = .none,3395 .tls = .none,
3366 },3396 },
3397 .archive = null,
3367 .nodes = .empty,3398 .nodes = .empty,
3368 .shdrs = .empty,3399 .shdrs = .empty,
3369 .phdrs = .empty,3400 .phdrs = .empty,
...@@ -3609,7 +3640,7 @@ fn initHeaders(...@@ -3609,7 +3640,7 @@ fn initHeaders(
3609 };3640 };
3610 };3641 };
36113642
3612 const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_footer3643 const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_member_header
3613 3 + // `.elf`, `.ehdr`, and `.shdr` nodes3644 3 + // `.elf`, `.ehdr`, and `.shdr` nodes
3614 (shnum - 1) + // -1 because the SHN_UNDEF shdr does not have a `.section` node3645 (shnum - 1) + // -1 because the SHN_UNDEF shdr does not have a `.section` node
3615 (phnum -| 1); // -1 because the GNU_STACK phdr does not have a `.segment` node3646 (phnum -| 1); // -1 because the GNU_STACK phdr does not have a `.segment` node
...@@ -3626,11 +3657,14 @@ fn initHeaders(...@@ -3626,11 +3657,14 @@ fn initHeaders(
3626 const archive_ni: MappedFile.Node.Index = .root;3657 const archive_ni: MappedFile.Node.Index = .root;
36273658
3628 const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{3659 const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{
3629 .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr) * 2,3660 // We intentionally do not set `.alignment = .@"2"` here, because the string table data
3630 .alignment = .@"2",3661 // in this node does not need to have an aligned length. (This node's offset is aligned
3631 .next_moved = true,3662 // regardless by virtue of it being a header.)
3632 .bubbles_moved = false,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.
3633 .enable_next_moved = true,3666 .enable_next_moved = true,
3667 .next_moved = true,
3634 });3668 });
3635 elf.nodes.appendAssumeCapacity(.archive_header);3669 elf.nodes.appendAssumeCapacity(.archive_header);
3636 const archive_header_slice = archive_header_ni.slice(&elf.mf);3670 const archive_header_slice = archive_header_ni.slice(&elf.mf);
...@@ -3642,23 +3676,47 @@ fn initHeaders(...@@ -3642,23 +3676,47 @@ fn initHeaders(
3642 .ar_uid = @splat(' '),3676 .ar_uid = @splat(' '),
3643 .ar_gid = @splat(' '),3677 .ar_gid = @splat(' '),
3644 .ar_mode = @splat(' '),3678 .ar_mode = @splat(' '),
3645 .ar_size = @splat(' '),3679 .ar_size = undefined, // populated by `flushNextMoved` for `archive_header_ni`
3646 .ar_fmag = std.elf.ARFMAG.*,3680 .ar_fmag = std.elf.ARFMAG.*,
3647 };3681 };
36483682
3649 elf.ni.elf = try archive_ni.addFloatingChild(&elf.mf, gpa, .{3683 elf.ni.elf = try archive_ni.addOnlyFooterChild(&elf.mf, gpa, .{
3650 .alignment = node_block_align.max(.@"2"),3684 .alignment = node_block_align.max(.@"2"),
3651 .next_moved = true,
3652 .bubbles_moved = false,3685 .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
3654 });3687 });
3655 elf.nodes.appendAssumeCapacity(.elf);3688 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), .{
3658 .alignment = .@"2",3691 .alignment = .@"2",
3659 .size = @sizeOf(std.elf.ar_hdr),3692 .size = @sizeOf(std.elf.ar_hdr),
3660 });3693 });
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);
3662 } else {3720 } else {
3663 elf.ni.elf = .root;3721 elf.ni.elf = .root;
3664 elf.nodes.appendAssumeCapacity(.elf);3722 elf.nodes.appendAssumeCapacity(.elf);
...@@ -4613,12 +4671,12 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {...@@ -4613,12 +4671,12 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {
4613 return switch (elf.getNode(ni)) {4671 return switch (elf.getNode(ni)) {
4614 .archive,4672 .archive,
4615 .archive_header,4673 .archive_header,
4616 .archive_elf_footer,4674 .archive_input_member,
4675 .archive_elf_member_header,
4617 .elf,4676 .elf,
4618 .ehdr,4677 .ehdr,
4619 .shdr,4678 .shdr,
4620 .segment,4679 .segment,
4621 .input_member,
4622 => unreachable,4680 => unreachable,
4623 .section => |shndx| shndx,4681 .section => |shndx| shndx,
4624 .input_section,4682 .input_section,
...@@ -4634,12 +4692,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {...@@ -4634,12 +4692,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
4634 return switch (elf.getNode(ni)) {4692 return switch (elf.getNode(ni)) {
4635 .archive,4693 .archive,
4636 .archive_header,4694 .archive_header,
4637 .archive_elf_footer,4695 .archive_input_member,
4696 .archive_elf_member_header,
4638 .elf,4697 .elf,
4639 .ehdr,4698 .ehdr,
4640 .shdr,4699 .shdr,
4641 .segment,4700 .segment,
4642 .input_member,
4643 .copied_global,4701 .copied_global,
4644 => unreachable,4702 => unreachable,
4645 .section => |shndx| shndx.vaddr(elf),4703 .section => |shndx| shndx.vaddr(elf),
...@@ -4653,14 +4711,18 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {...@@ -4653,14 +4711,18 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
4653}4711}
4654fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {4712fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
4655 const parent_vaddr = switch (elf.getNode(ni.parent(&elf.mf).unwrap().?)) {4713 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,
4657 .elf => return 0,4719 .elf => return 0,
4658 .ehdr, .shdr => unreachable,4720 .ehdr, .shdr => unreachable,
4659 .segment => |phndx| switch (elf.phdrSlice()) {4721 .segment => |phndx| switch (elf.phdrSlice()) {
4660 inline else => |phdr| elf.targetLoad(&phdr[phndx].vaddr),4722 inline else => |phdr| elf.targetLoad(&phdr[phndx].vaddr),
4661 },4723 },
4662 .section => |shndx| if (shndx == elf.shndx.tdata) 0 else shndx.vaddr(elf),4724 .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,
4664 inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf),4726 inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf),
4665 };4727 };
4666 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);4728 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);
...@@ -4679,12 +4741,12 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void {...@@ -4679,12 +4741,12 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void {
4679 const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) {4741 const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) {
4680 .archive,4742 .archive,
4681 .archive_header,4743 .archive_header,
4682 .archive_elf_footer,4744 .archive_input_member,
4745 .archive_elf_member_header,
4683 .elf,4746 .elf,
4684 .ehdr,4747 .ehdr,
4685 .shdr,4748 .shdr,
4686 .segment,4749 .segment,
4687 .input_member,
4688 .copied_global,4750 .copied_global,
4689 => unreachable, // cannot contain relocs4751 => unreachable, // cannot contain relocs
4690 .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported)4752 .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported)
...@@ -4745,7 +4807,12 @@ fn flushMovedNodeRelocs(...@@ -4745,7 +4807,12 @@ fn flushMovedNodeRelocs(
4745 // changed, so update the `offset` field of the `ElfN.Rela` entry.4807 // changed, so update the `offset` field of the `ElfN.Rela` entry.
4746 reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset);4808 reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset);
4747 }4809 }
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 }
4749 }4816 }
4750 }4817 }
47514818
...@@ -5035,16 +5102,6 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr {...@@ -5035,16 +5102,6 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr {
5035 }5102 }
5036}5103}
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
5048const SymPtr = union(std.elf.CLASS) {5105const SymPtr = union(std.elf.CLASS) {
5049 NONE: noreturn,5106 NONE: noreturn,
5050 @"32": *std.elf.Elf32.Sym,5107 @"32": *std.elf.Elf32.Sym,
...@@ -5544,19 +5601,61 @@ fn loadObject(...@@ -5544,19 +5601,61 @@ fn loadObject(
5544 .member = if (member) |m| try gpa.dupe(u8, m) else null,5601 .member = if (member) |m| try gpa.dupe(u8, m) else null,
5545 .extra = undefined,5602 .extra = undefined,
5546 };5603 };
5547 if (elf.ni.elf != .root) {5604 if (elf.archive) |*archive| {
5548 const archive_ni: MappedFile.Node.Index = .root;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
5549 try elf.nodes.ensureUnusedCapacity(gpa, 1);5616 try elf.nodes.ensureUnusedCapacity(gpa, 1);
5550 input.extra = .{ .node = try archive_ni.addFloatingChild(&elf.mf, gpa, .{5617 const new_member_ni = try archive.ni.addFooterChildBefore(&elf.mf, gpa, first_member_oni, .{
5551 .size = Alignment.@"2".forward(fl.size + @sizeOf(std.elf.ar_hdr)),5618 .size = Alignment.@"2".forward(@sizeOf(std.elf.ar_hdr) + fl.size),
5552 .alignment = .@"2",5619 .alignment = .@"2",
5553 .next_moved = true,5620 });
5554 .bubbles_moved = false,5621 elf.nodes.appendAssumeCapacity(.{ .archive_input_member = input_index });
5555 .enable_next_moved = true,5622 input.extra = .{ .node = new_member_ni };
5556 }) };
5557 elf.nodes.appendAssumeCapacity(.{ .input_member = input_index });
5558 elf.input_prog_node.increaseEstimatedTotalItems(1);5623 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
5560 // Since we are not emitting the archive symbol table (yet?) we do not need to parse5659 // Since we are not emitting the archive symbol table (yet?) we do not need to parse
5561 // the symbols in this input.5660 // the symbols in this input.
5562 return;5661 return;
...@@ -5969,6 +6068,46 @@ fn loadObject(...@@ -5969,6 +6068,46 @@ fn loadObject(
5969 },6068 },
5970 }6069 }
5971}6070}
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}
5972fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {6111fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {
5973 const comp = elf.base.comp;6112 const comp = elf.base.comp;
5974 const gpa = comp.gpa;6113 const gpa = comp.gpa;
...@@ -7136,12 +7275,12 @@ fn addGotRelocAssumeCapacity(...@@ -7136,12 +7275,12 @@ fn addGotRelocAssumeCapacity(
7136 switch (elf.getNode(node)) {7275 switch (elf.getNode(node)) {
7137 .archive,7276 .archive,
7138 .archive_header,7277 .archive_header,
7139 .archive_elf_footer,7278 .archive_input_member,
7279 .archive_elf_member_header,
7140 .elf,7280 .elf,
7141 .ehdr,7281 .ehdr,
7142 .shdr,7282 .shdr,
7143 .segment,7283 .segment,
7144 .input_member,
7145 .copied_global,7284 .copied_global,
7146 => unreachable, // cannot contain relocs,7285 => unreachable, // cannot contain relocs,
7147 .section,7286 .section,
...@@ -7579,6 +7718,17 @@ fn flushInner(...@@ -7579,6 +7718,17 @@ fn flushInner(
7579 diags.addError("failed to apply {d} relocations: misaligned value", .{elf.misaligned_reloc_count});7718 diags.addError("failed to apply {d} relocations: misaligned value", .{elf.misaligned_reloc_count});
7580 }7719 }
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
7582 elf.flushDynamic();7732 elf.flushDynamic();
75837733
7584 const entry_addr: u64 = entry: {7734 const entry_addr: u64 = entry: {
...@@ -7610,6 +7760,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool {...@@ -7610,6 +7760,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool {
7610 const comp = elf.base.comp;7760 const comp = elf.base.comp;
7611 const diags = &comp.link_diags;7761 const diags = &comp.link_diags;
76127762
7763 elf.mf.nodes_lock.lock();
7764 defer elf.mf.nodes_lock.unlock();
7765
7613 assert(elf.pending_uavs.items.len == 0);7766 assert(elf.pending_uavs.items.len == 0);
7614 for (&elf.lazy.values) |*lazy| {7767 for (&elf.lazy.values) |*lazy| {
7615 assert(lazy.pending_index == lazy.map.count());7768 assert(lazy.pending_index == lazy.map.count());
...@@ -7763,7 +7916,7 @@ fn idleProgNode(...@@ -7763,7 +7916,7 @@ fn idleProgNode(
7763 return prog_node.start(name: switch (node) {7916 return prog_node.start(name: switch (node) {
7764 else => |tag| @tagName(tag),7917 else => |tag| @tagName(tag),
7765 .section => |shndx| shndx.name(elf).slice(elf),7918 .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}", .{
7767 ii.path(elf).fmtEscapeString(),7920 ii.path(elf).fmtEscapeString(),
7768 fmtMemberString(ii.member(elf)),7921 fmtMemberString(ii.member(elf)),
7769 }) catch &name,7922 }) catch &name,
...@@ -7890,7 +8043,6 @@ fn genLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) Error!void {...@@ -7890,7 +8043,6 @@ fn genLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) Error!void {
7890fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {8043fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {
7891 const comp = elf.base.comp;8044 const comp = elf.base.comp;
7892 const io = comp.io;8045 const io = comp.io;
7893 const gpa = comp.gpa;
7894 const diags = &comp.link_diags;8046 const diags = &comp.link_diags;
7895 const path = ii.path(elf);8047 const path = ii.path(elf);
7896 const file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| switch (err) {8048 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 {...@@ -7898,23 +8050,40 @@ fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {
7898 else => |e| return diags.fail("failed to open input file \"{f}\": {t}", .{ path.fmtEscapeString(), e }),8050 else => |e| return diags.fail("failed to open input file \"{f}\": {t}", .{ path.fmtEscapeString(), e }),
7899 };8051 };
7900 defer file.close(io);8052 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
7901 var fr = file.reader(io, &.{});8071 var fr = file.reader(io, &.{});
7902 var nw: MappedFile.Node.Writer = undefined;8072 var w: Io.Writer = .fixed(slice[@sizeOf(std.elf.ar_hdr)..]);
7903 ii.node(elf).writer(&elf.mf, gpa, &nw);8073 const n_bytes_read = w.sendFileAll(&fr, .limited(input_size)) catch |err| switch (err) {
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) {
7907 error.ReadFailed => return diags.fail("failed to read input \"{f}{f}\": {t}", .{8074 error.ReadFailed => return diags.fail("failed to read input \"{f}{f}\": {t}", .{
7908 path.fmtEscapeString(),8075 path.fmtEscapeString(),
7909 fmtMemberString(ii.member(elf)),8076 fmtMemberString(ii.member(elf)),
7910 fr.err orelse (fr.seek_err orelse fr.size_err.?),8077 fr.err orelse (fr.seek_err orelse fr.size_err.?),
7911 }),8078 }),
7912 error.WriteFailed => return nw.err.?,8079 error.WriteFailed => unreachable, // `.limited(input_size)` prevents us writing too many bytes
7913 };8080 };
7914 if (n_bytes + 1 < size) return diags.fail("failed to read input \"{f}{f}\": unexpected eof", .{8081 if (n_bytes_read != input_size) {
7915 path.fmtEscapeString(),8082 return diags.fail("failed to load input \"{f}{f}\": file truncated during compilation", .{
7916 fmtMemberString(ii.member(elf)),8083 path.fmtEscapeString(),
7917 });8084 fmtMemberString(ii.member(elf)),
8085 });
8086 }
7918}8087}
79198088
7920fn flushInputSection(elf: *Elf, isi: InputSection.Index) Error!void {8089fn 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...@@ -7996,12 +8165,18 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
7996 const trace = tracy.trace(@src());8165 const trace = tracy.trace(@src());
7997 defer trace.end();8166 defer trace.end();
79988167
7999 elf.mf.nodes_lock.lock();
8000 defer elf.mf.nodes_lock.unlock();
8001
8002 switch (elf.getNode(ni)) {8168 switch (elf.getNode(ni)) {
8003 .archive, .archive_header => unreachable,8169 .archive => unreachable,
8004 .archive_elf_footer, .elf => {},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
8005 .ehdr, .shdr => elf.flushElfOffset(ni),8180 .ehdr, .shdr => elf.flushElfOffset(ni),
8006 .segment => |phndx| {8181 .segment => |phndx| {
8007 elf.flushElfOffset(ni);8182 elf.flushElfOffset(ni);
...@@ -8079,7 +8254,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -8079,7 +8254,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
8079 elf.flushMovedPltSection(.plt_sec, old_addr, addr);8254 elf.flushMovedPltSection(.plt_sec, old_addr, addr);
8080 }8255 }
8081 },8256 },
8082 .input_member => {},
8083 .input_section => |isi| {8257 .input_section => |isi| {
8084 const old_section_addr = isi.ptr(elf).vaddr;8258 const old_section_addr = isi.ptr(elf).vaddr;
8085 const new_section_addr = elf.computeNodeVAddr(ni);8259 const new_section_addr = elf.computeNodeVAddr(ni);
...@@ -8320,24 +8494,23 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo...@@ -8320,24 +8494,23 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
8320 const trace = tracy.trace(@src());8494 const trace = tracy.trace(@src());
8321 defer trace.end();8495 defer trace.end();
83228496
8323 elf.mf.nodes_lock.lock();
8324 defer elf.mf.nodes_lock.unlock();
8325
8326 _, const size = ni.location(&elf.mf).resolve(&elf.mf);8497 _, const size = ni.location(&elf.mf).resolve(&elf.mf);
8327 switch (elf.getNode(ni)) {8498 switch (elf.getNode(ni)) {
8328 .archive => {8499 .archive, .archive_header => {},
8329 if (ni.last(&elf.mf).unwrap()) |last_ni| {8500 .archive_input_member => unreachable,
8330 if (last_ni.prev(&elf.mf).unwrap()) |prev_ni| {8501 .archive_elf_member_header => unreachable,
8331 if (prev_ni.hasNextMoved(&elf.mf)) return;8502 .elf => if (elf.archive) |*archive| {
8332 }8503 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
8333 const offset, _ = last_ni.location(&elf.mf).resolve(&elf.mf);8504 archive.elf_member_header_ni.slice(&elf.mf),
8334 _ = std.mem.print(&elf.arHdrPtr(last_ni).ar_size, "{d:<10}", .{8505 );
8335 size - offset,8506 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{size})) |size_str| {
8336 }) catch @panic("archive member too large");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,
8337 }8511 }
8338 },8512 },
8339 .archive_header, .elf => {},8513 .ehdr => unreachable,
8340 .ehdr, .archive_elf_footer => unreachable,
8341 .shdr => {},8514 .shdr => {},
8342 .segment => |phndx| switch (elf.phdrSlice()) {8515 .segment => |phndx| switch (elf.phdrSlice()) {
8343 inline else => |phdr| {8516 inline else => |phdr| {
...@@ -8409,7 +8582,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo...@@ -8409,7 +8582,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
8409 }8582 }
8410 },8583 },
8411 },8584 },
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 => {},
8413 }8586 }
8414}8587}
84158588
...@@ -8417,12 +8590,11 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!...@@ -8417,12 +8590,11 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!
8417 const trace = tracy.trace(@src());8590 const trace = tracy.trace(@src());
8418 defer trace.end();8591 defer trace.end();
84198592
8420 elf.mf.nodes_lock.lock();
8421 defer elf.mf.nodes_lock.unlock();
8422
8423 switch (elf.getNode(ni)) {8593 switch (elf.getNode(ni)) {
8424 .archive,8594 .archive,
8425 .archive_elf_footer,8595 .archive_input_member,
8596 .archive_elf_member_header,
8597 .elf,
8426 .ehdr,8598 .ehdr,
8427 .shdr,8599 .shdr,
8428 .segment,8600 .segment,
...@@ -8434,54 +8606,32 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!...@@ -8434,54 +8606,32 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!
8434 .lazy_code,8606 .lazy_code,
8435 .lazy_const_data,8607 .lazy_const_data,
8436 => unreachable,8608 => unreachable,
8437 .archive_header, .elf, .input_member => |_, tag| {8609
8438 const member_offset, const update_size = member_offset: {8610 .archive_header => {
8439 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);8611 const archive = &elf.archive.?;
8440 break :member_offset switch (tag) {8612
8441 else => unreachable,8613 // Because we can't just throw padding bytes in the middle of an archive file, we need
8442 .archive_header => .{ offset + std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr), true },8614 // the member name string table (the "//" member) to absorb all the padding bytes
8443 .elf, .input_member => .{ offset, !ni.prev(&elf.mf).unwrap().?.hasNextMoved(&elf.mf) },8615 // between it (in the `.archive_header` node) and the first actual member.
8444 };8616 const next_member_ni = ni.next(&elf.mf).unwrap() orelse {
8445 };8617 // I guess there are no link inputs yet? But there will be eventually!
8446 const member_size = if (ni.next(&elf.mf).unwrap()) |next_ni| member_size: {8618 return;
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;
8481 };8619 };
8482 if (update_size) _ = std.mem.print(&elf.arHdrPtr(ni).ar_size, "{d:<10}", .{8620 const next_member_offset: u64, _ = next_member_ni.location(&elf.mf).resolve(&elf.mf);
8483 member_size,8621 const strtab_member_offset = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr);
8484 }) catch @panic("archive member too large");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 }
8485 },8635 },
8486 }8636 }
8487}8637}