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-25 22:59:25+01:00
log6f8eb736856054b8a02ae2fe734340e2f8ce0a50
treef28812ba3b12bc753ad3d34cfc0702fd87352f42
parent18e38104ce60a02f27b565097cff66c3429aa8d4
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: rework archives

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,
...@@ -3602,7 +3633,7 @@ fn initHeaders(...@@ -3602,7 +3633,7 @@ fn initHeaders(
3602 }, phnum };3633 }, phnum };
3603 };3634 };
36043635
3605 const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_footer3636 const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_member_header
3606 3 + // `.elf`, `.ehdr`, and `.shdr` nodes3637 3 + // `.elf`, `.ehdr`, and `.shdr` nodes
3607 (shnum - 1) + // -1 because the SHN_UNDEF shdr does not have a `.section` node3638 (shnum - 1) + // -1 because the SHN_UNDEF shdr does not have a `.section` node
3608 (phnum -| 1); // -1 because the GNU_STACK phdr does not have a `.segment` node3639 (phnum -| 1); // -1 because the GNU_STACK phdr does not have a `.segment` node
...@@ -3619,11 +3650,14 @@ fn initHeaders(...@@ -3619,11 +3650,14 @@ fn initHeaders(
3619 const archive_ni: MappedFile.Node.Index = .root;3650 const archive_ni: MappedFile.Node.Index = .root;
36203651
3621 const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{3652 const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{
3622 .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr) * 2,3653 // We intentionally do not set `.alignment = .@"2"` here, because the string table data
3623 .alignment = .@"2",3654 // in this node does not need to have an aligned length. (This node's offset is aligned
3624 .next_moved = true,3655 // regardless by virtue of it being a header.)
3625 .bubbles_moved = false,3656 .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr),
3657 // The archive header uses 'next_moved' events to resize the "//" member, so that it
3658 // absorbs all padding between `archive_header_ni` and the actual object file members.
3626 .enable_next_moved = true,3659 .enable_next_moved = true,
3660 .next_moved = true,
3627 });3661 });
3628 elf.nodes.appendAssumeCapacity(.archive_header);3662 elf.nodes.appendAssumeCapacity(.archive_header);
3629 const archive_header_slice = archive_header_ni.slice(&elf.mf);3663 const archive_header_slice = archive_header_ni.slice(&elf.mf);
...@@ -3635,23 +3669,47 @@ fn initHeaders(...@@ -3635,23 +3669,47 @@ fn initHeaders(
3635 .ar_uid = @splat(' '),3669 .ar_uid = @splat(' '),
3636 .ar_gid = @splat(' '),3670 .ar_gid = @splat(' '),
3637 .ar_mode = @splat(' '),3671 .ar_mode = @splat(' '),
3638 .ar_size = @splat(' '),3672 .ar_size = undefined, // populated by `flushNextMoved` for `archive_header_ni`
3639 .ar_fmag = std.elf.ARFMAG.*,3673 .ar_fmag = std.elf.ARFMAG.*,
3640 };3674 };
36413675
3642 elf.ni.elf = try archive_ni.addFloatingChild(&elf.mf, gpa, .{3676 elf.ni.elf = try archive_ni.addOnlyFooterChild(&elf.mf, gpa, .{
3643 .alignment = node_block_align.max(.@"2"),3677 .alignment = node_block_align.max(.@"2"),
3644 .next_moved = true,
3645 .bubbles_moved = false,3678 .bubbles_moved = false,
3646 .enable_next_moved = true,3679 .resized = true, // ensure that this node's `ar_hdr.ar_size` is updated at least once
3647 });3680 });
3648 elf.nodes.appendAssumeCapacity(.elf);3681 elf.nodes.appendAssumeCapacity(.elf);
36493682
3650 _ = try elf.ni.elf.addOnlyFooterChild(&elf.mf, gpa, .{3683 const elf_ar_hdr_ni = try archive_ni.addFooterChildBefore(&elf.mf, gpa, .wrap(elf.ni.elf), .{
3651 .alignment = .@"2",3684 .alignment = .@"2",
3652 .size = @sizeOf(std.elf.ar_hdr),3685 .size = @sizeOf(std.elf.ar_hdr),
3653 });3686 });
3654 elf.nodes.appendAssumeCapacity(.archive_elf_footer);3687 elf.nodes.appendAssumeCapacity(.archive_elf_member_header);
3688
3689 // Must be populated before we call `populateArchiveMemberName` below.
3690 elf.archive = .{
3691 .ni = archive_ni,
3692 .header_ni = archive_header_ni,
3693 .elf_member_header_ni = elf_ar_hdr_ni,
3694
3695 .elf_member_too_big = false,
3696 .strtab_member_too_big = false,
3697 };
3698
3699 const elf_ar_hdr: *std.elf.ar_hdr = @ptrCast(elf_ar_hdr_ni.slice(&elf.mf));
3700 elf_ar_hdr.* = .{
3701 .ar_name = undefined, // populated below
3702 .ar_date = "0 ".*,
3703 .ar_uid = "0 ".*,
3704 .ar_gid = "0 ".*,
3705 .ar_mode = "644 ".*,
3706 .ar_size = undefined, // populated by `flushResized` for the `.elf` node
3707 .ar_fmag = std.elf.ARFMAG.*,
3708 };
3709 const zcu_member_name = try std.fmt.allocPrint(gpa, "{s}_zcu.o", .{comp.root_name});
3710 defer gpa.free(zcu_member_name);
3711 // After this call returns, `elf_ar_hdr` is invalidated.
3712 try elf.populateArchiveMemberName(elf_ar_hdr, zcu_member_name);
3655 } else {3713 } else {
3656 elf.ni.elf = .root;3714 elf.ni.elf = .root;
3657 elf.nodes.appendAssumeCapacity(.elf);3715 elf.nodes.appendAssumeCapacity(.elf);
...@@ -4573,12 +4631,12 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {...@@ -4573,12 +4631,12 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {
4573 return switch (elf.getNode(ni)) {4631 return switch (elf.getNode(ni)) {
4574 .archive,4632 .archive,
4575 .archive_header,4633 .archive_header,
4576 .archive_elf_footer,4634 .archive_input_member,
4635 .archive_elf_member_header,
4577 .elf,4636 .elf,
4578 .ehdr,4637 .ehdr,
4579 .shdr,4638 .shdr,
4580 .segment,4639 .segment,
4581 .input_member,
4582 => unreachable,4640 => unreachable,
4583 .section => |shndx| shndx,4641 .section => |shndx| shndx,
4584 .input_section,4642 .input_section,
...@@ -4594,12 +4652,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {...@@ -4594,12 +4652,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
4594 return switch (elf.getNode(ni)) {4652 return switch (elf.getNode(ni)) {
4595 .archive,4653 .archive,
4596 .archive_header,4654 .archive_header,
4597 .archive_elf_footer,4655 .archive_input_member,
4656 .archive_elf_member_header,
4598 .elf,4657 .elf,
4599 .ehdr,4658 .ehdr,
4600 .shdr,4659 .shdr,
4601 .segment,4660 .segment,
4602 .input_member,
4603 .copied_global,4661 .copied_global,
4604 => unreachable,4662 => unreachable,
4605 .section => |shndx| shndx.vaddr(elf),4663 .section => |shndx| shndx.vaddr(elf),
...@@ -4613,14 +4671,18 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {...@@ -4613,14 +4671,18 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
4613}4671}
4614fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {4672fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
4615 const parent_vaddr = switch (elf.getNode(ni.parent(&elf.mf).unwrap().?)) {4673 const parent_vaddr = switch (elf.getNode(ni.parent(&elf.mf).unwrap().?)) {
4616 .archive, .archive_header, .archive_elf_footer => unreachable,4674 .archive,
4675 .archive_header,
4676 .archive_input_member,
4677 .archive_elf_member_header,
4678 => unreachable,
4617 .elf => return 0,4679 .elf => return 0,
4618 .ehdr, .shdr => unreachable,4680 .ehdr, .shdr => unreachable,
4619 .segment => |phndx| switch (elf.phdrSlice()) {4681 .segment => |phndx| switch (elf.phdrSlice()) {
4620 inline else => |phdr| elf.targetLoad(&phdr[phndx].vaddr),4682 inline else => |phdr| elf.targetLoad(&phdr[phndx].vaddr),
4621 },4683 },
4622 .section => |shndx| if (shndx == elf.shndx.tdata) 0 else shndx.vaddr(elf),4684 .section => |shndx| if (shndx == elf.shndx.tdata) 0 else shndx.vaddr(elf),
4623 .input_member, .input_section, .copied_global => unreachable,4685 .input_section, .copied_global => unreachable,
4624 inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf),4686 inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf),
4625 };4687 };
4626 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);4688 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);
...@@ -4639,12 +4701,12 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void {...@@ -4639,12 +4701,12 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void {
4639 const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) {4701 const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) {
4640 .archive,4702 .archive,
4641 .archive_header,4703 .archive_header,
4642 .archive_elf_footer,4704 .archive_input_member,
4705 .archive_elf_member_header,
4643 .elf,4706 .elf,
4644 .ehdr,4707 .ehdr,
4645 .shdr,4708 .shdr,
4646 .segment,4709 .segment,
4647 .input_member,
4648 .copied_global,4710 .copied_global,
4649 => unreachable, // cannot contain relocs4711 => unreachable, // cannot contain relocs
4650 .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported)4712 .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported)
...@@ -4705,7 +4767,12 @@ fn flushMovedNodeRelocs(...@@ -4705,7 +4767,12 @@ fn flushMovedNodeRelocs(
4705 // changed, so update the `offset` field of the `ElfN.Rela` entry.4767 // changed, so update the `offset` field of the `ElfN.Rela` entry.
4706 reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset);4768 reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset);
4707 }4769 }
4708 reloc.apply(elf);4770 // This is not just the inverse of the above condition, because if `reloc` is relative
4771 // to the base of this DSO, then `rela_index` is an `R_*_RELATIVE` relocation, but we
4772 // still need to call `SymbolReloc.apply` to update that relocation's addend.
4773 if (elf.ehdrType() != .REL) {
4774 reloc.apply(elf);
4775 }
4709 }4776 }
4710 }4777 }
47114778
...@@ -4971,16 +5038,6 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr {...@@ -4971,16 +5038,6 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr {
4971 }5038 }
4972}5039}
49735040
4974fn arHdrPtr(elf: *Elf, ni: MappedFile.Node.Index) *align(2) std.elf.ar_hdr {
4975 assert(elf.ni.elf != .root);
4976 const file_offset = ni.fileLocation(&elf.mf, false).offset;
4977 return @ptrCast(@alignCast(elf.mf.memory_map.memory[@intCast(switch (elf.getNode(ni)) {
4978 else => unreachable,
4979 .archive_header => file_offset + std.elf.ARMAG.len,
4980 .elf, .input_member => file_offset - @sizeOf(std.elf.ar_hdr),
4981 })..][0..@sizeOf(std.elf.ar_hdr)]));
4982}
4983
4984const SymPtr = union(std.elf.CLASS) {5041const SymPtr = union(std.elf.CLASS) {
4985 NONE: noreturn,5042 NONE: noreturn,
4986 @"32": *std.elf.Elf32.Sym,5043 @"32": *std.elf.Elf32.Sym,
...@@ -5480,19 +5537,61 @@ fn loadObject(...@@ -5480,19 +5537,61 @@ fn loadObject(
5480 .member = if (member) |m| try gpa.dupe(u8, m) else null,5537 .member = if (member) |m| try gpa.dupe(u8, m) else null,
5481 .extra = undefined,5538 .extra = undefined,
5482 };5539 };
5483 if (elf.ni.elf != .root) {5540 if (elf.archive) |*archive| {
5484 const archive_ni: MappedFile.Node.Index = .root;5541 // We're creating a static library, so just add this input as an archive member.
5542 assert(member == null); // don't try to put static library members into other static libraries
5543
5544 const first_member_oni = archive.header_ni.next(&elf.mf);
5545
5546 if (first_member_oni.unwrap()) |first_member_ni| switch (elf.getNode(first_member_ni)) {
5547 .archive_input_member, .archive_elf_member_header => {},
5548 .elf => unreachable, // always preceded by `.archive_elf_member_header`
5549 else => unreachable, // never a child of `.archive`
5550 };
5551
5485 try elf.nodes.ensureUnusedCapacity(gpa, 1);5552 try elf.nodes.ensureUnusedCapacity(gpa, 1);
5486 input.extra = .{ .node = try archive_ni.addFloatingChild(&elf.mf, gpa, .{5553 const new_member_ni = try archive.ni.addFooterChildBefore(&elf.mf, gpa, first_member_oni, .{
5487 .size = Alignment.@"2".forward(fl.size + @sizeOf(std.elf.ar_hdr)),5554 .size = Alignment.@"2".forward(@sizeOf(std.elf.ar_hdr) + fl.size),
5488 .alignment = .@"2",5555 .alignment = .@"2",
5489 .next_moved = true,5556 });
5490 .bubbles_moved = false,5557 elf.nodes.appendAssumeCapacity(.{ .archive_input_member = input_index });
5491 .enable_next_moved = true,5558 input.extra = .{ .node = new_member_ni };
5492 }) };
5493 elf.nodes.appendAssumeCapacity(.{ .input_member = input_index });
5494 elf.input_prog_node.increaseEstimatedTotalItems(1);5559 elf.input_prog_node.increaseEstimatedTotalItems(1);
54955560
5561 // The contents of the input will be written to the file by an idle task (`flushInput`), but
5562 // we do need to write the input's archive member header (`ar_hdr`) now, for two reasons:
5563 //
5564 // * If the input file has a long name, we need to add it to the archive member name string
5565 // table, which must happen deterministically (i.e. not in an idle task).
5566 //
5567 // * `flushInput` needs to know the actual file size (before padding to the alignment).
5568 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
5569 new_member_ni.slice(&elf.mf)[0..@sizeOf(std.elf.ar_hdr)],
5570 );
5571 member_ar_hdr.* = .{
5572 .ar_name = undefined, // populated below
5573 .ar_date = "0 ".*,
5574 .ar_uid = "0 ".*,
5575 .ar_gid = "0 ".*,
5576 .ar_mode = "644 ".*,
5577 .ar_size = undefined, // populated below
5578 .ar_fmag = std.elf.ARFMAG.*,
5579 };
5580
5581 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{fl.size})) |size_str| {
5582 @memset(member_ar_hdr.ar_size[size_str.len..], ' ');
5583 } else |err| switch (err) {
5584 error.NoSpaceLeft => return diags.failParse(
5585 path,
5586 "file size of {Bi} exceeds maximum size of archive member",
5587 .{fl.size},
5588 ),
5589 }
5590
5591 const member_name = std.fs.path.basename(path.sub_path);
5592 // After this call returns, `member_ar_hdr` is invalidated.
5593 try elf.populateArchiveMemberName(member_ar_hdr, member_name);
5594
5496 // Since we are not emitting the archive symbol table (yet?) we do not need to parse5595 // Since we are not emitting the archive symbol table (yet?) we do not need to parse
5497 // the symbols in this input.5596 // the symbols in this input.
5498 return;5597 return;
...@@ -5905,6 +6004,46 @@ fn loadObject(...@@ -5905,6 +6004,46 @@ fn loadObject(
5905 },6004 },
5906 }6005 }
5907}6006}
6007/// This function may resize the archive header, so therefore invalidates `member_ar_hdr`.
6008fn populateArchiveMemberName(elf: *Elf, member_ar_hdr: *std.elf.ar_hdr, member_name: []const u8) Error!void {
6009 if (std.mem.print(&member_ar_hdr.ar_name, "{s}/", .{member_name})) |name_str| {
6010 @memset(member_ar_hdr.ar_name[name_str.len..], ' ');
6011 return;
6012 } else |err| switch (err) {
6013 error.NoSpaceLeft => {}, // handled below
6014 }
6015
6016 const gpa = elf.base.comp.gpa;
6017 const archive_header_ni = elf.archive.?.header_ni;
6018
6019 // The member's name is too big to put directly in the `ar_name` field, so it needs to go in the
6020 // "long name" string table instead (in the special member named "//").
6021
6022 _, const old_archive_header_size = archive_header_ni.location(&elf.mf).resolve(&elf.mf);
6023
6024 // We're going to add a new string at the end of the table. Update `member_ar_hdr` first,
6025 // because resizing the string table will invalidate it.
6026 const string_table_offset = old_archive_header_size - (std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr));
6027 if (std.mem.print(&member_ar_hdr.ar_name, "/{d}", .{string_table_offset})) |name_str| {
6028 @memset(member_ar_hdr.ar_name[name_str.len..], ' ');
6029 } else |inner_err| switch (inner_err) {
6030 error.NoSpaceLeft => {
6031 // The string table offset is itself too big to represent. This means the string table's
6032 // *size* is definitely too big to represent (we only get 10 bytes for that whereas we
6033 // get 16 here!), so as long as we still add the string, we're guaranteed to get a link
6034 // error for that reason. Therefore, we can just ignore this error and carry on.
6035 },
6036 }
6037
6038 // We set the size of the archive header node exactly, because we want padding bytes to go into
6039 // the root `.archive` node. That way, those bytes could still be used to grow the string table
6040 // if necessary, but they could also be used for new archive members.
6041 try archive_header_ni.resizeLeaf(&elf.mf, gpa, old_archive_header_size + member_name.len + 2);
6042
6043 const dest_slice = archive_header_ni.slice(&elf.mf)[old_archive_header_size..];
6044 @memcpy(dest_slice[0 .. dest_slice.len - 2], member_name);
6045 @memcpy(dest_slice[dest_slice.len - 2 ..], "/\n"); // yes, the terminator is weird
6046}
5908fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {6047fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {
5909 const comp = elf.base.comp;6048 const comp = elf.base.comp;
5910 const gpa = comp.gpa;6049 const gpa = comp.gpa;
...@@ -7072,12 +7211,12 @@ fn addGotRelocAssumeCapacity(...@@ -7072,12 +7211,12 @@ fn addGotRelocAssumeCapacity(
7072 switch (elf.getNode(node)) {7211 switch (elf.getNode(node)) {
7073 .archive,7212 .archive,
7074 .archive_header,7213 .archive_header,
7075 .archive_elf_footer,7214 .archive_input_member,
7215 .archive_elf_member_header,
7076 .elf,7216 .elf,
7077 .ehdr,7217 .ehdr,
7078 .shdr,7218 .shdr,
7079 .segment,7219 .segment,
7080 .input_member,
7081 .copied_global,7220 .copied_global,
7082 => unreachable, // cannot contain relocs,7221 => unreachable, // cannot contain relocs,
7083 .section,7222 .section,
...@@ -7514,6 +7653,17 @@ fn flushInner(...@@ -7514,6 +7653,17 @@ fn flushInner(
7514 diags.addError("failed to apply {d} relocations: misaligned value", .{elf.misaligned_reloc_count});7653 diags.addError("failed to apply {d} relocations: misaligned value", .{elf.misaligned_reloc_count});
7515 }7654 }
75167655
7656 if (elf.archive) |*archive| {
7657 if (archive.elf_member_too_big) diags.addError(
7658 "file size of {Bi} exceeds maximum size of archive member",
7659 .{elf.ni.elf.location(&elf.mf).resolve(&elf.mf)[1]},
7660 );
7661 if (archive.strtab_member_too_big) diags.addError(
7662 "archive file name string table exceeds maximum size",
7663 .{},
7664 );
7665 }
7666
7517 elf.flushDynamic();7667 elf.flushDynamic();
75187668
7519 const entry_addr: u64 = entry: {7669 const entry_addr: u64 = entry: {
...@@ -7545,6 +7695,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool {...@@ -7545,6 +7695,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool {
7545 const comp = elf.base.comp;7695 const comp = elf.base.comp;
7546 const diags = &comp.link_diags;7696 const diags = &comp.link_diags;
75477697
7698 elf.mf.nodes_lock.lock();
7699 defer elf.mf.nodes_lock.unlock();
7700
7548 assert(elf.pending_uavs.items.len == 0);7701 assert(elf.pending_uavs.items.len == 0);
7549 for (&elf.lazy.values) |*lazy| {7702 for (&elf.lazy.values) |*lazy| {
7550 assert(lazy.pending_index == lazy.map.count());7703 assert(lazy.pending_index == lazy.map.count());
...@@ -7698,7 +7851,7 @@ fn idleProgNode(...@@ -7698,7 +7851,7 @@ fn idleProgNode(
7698 return prog_node.start(name: switch (node) {7851 return prog_node.start(name: switch (node) {
7699 else => |tag| @tagName(tag),7852 else => |tag| @tagName(tag),
7700 .section => |shndx| shndx.name(elf).slice(elf),7853 .section => |shndx| shndx.name(elf).slice(elf),
7701 .input_member => |ii| std.fmt.bufPrint(&name, "{f}{f}", .{7854 .archive_input_member => |ii| std.fmt.bufPrint(&name, "{f}{f}", .{
7702 ii.path(elf).fmtEscapeString(),7855 ii.path(elf).fmtEscapeString(),
7703 fmtMemberString(ii.member(elf)),7856 fmtMemberString(ii.member(elf)),
7704 }) catch &name,7857 }) catch &name,
...@@ -7825,7 +7978,6 @@ fn genLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) Error!void {...@@ -7825,7 +7978,6 @@ fn genLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) Error!void {
7825fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {7978fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {
7826 const comp = elf.base.comp;7979 const comp = elf.base.comp;
7827 const io = comp.io;7980 const io = comp.io;
7828 const gpa = comp.gpa;
7829 const diags = &comp.link_diags;7981 const diags = &comp.link_diags;
7830 const path = ii.path(elf);7982 const path = ii.path(elf);
7831 const file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| switch (err) {7983 const file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| switch (err) {
...@@ -7833,23 +7985,40 @@ fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {...@@ -7833,23 +7985,40 @@ fn flushInput(elf: *Elf, ii: Node.InputIndex) Error!void {
7833 else => |e| return diags.fail("failed to open input file \"{f}\": {t}", .{ path.fmtEscapeString(), e }),7985 else => |e| return diags.fail("failed to open input file \"{f}\": {t}", .{ path.fmtEscapeString(), e }),
7834 };7986 };
7835 defer file.close(io);7987 defer file.close(io);
7988
7989 const slice = ii.node(elf).slice(&elf.mf);
7990
7991 const member_ar_hdr: *const std.elf.ar_hdr = @ptrCast(slice[0..@sizeOf(std.elf.ar_hdr)]);
7992 const input_size: u32 = member_ar_hdr.size() catch |err| switch (err) {
7993 // We wrote the `ar_hdr` ourselves (in `loadObject`), so it is definitely valid.
7994 error.Overflow, error.InvalidCharacter => unreachable,
7995 };
7996
7997 switch (slice.len - @sizeOf(std.elf.ar_hdr) - input_size) {
7998 0 => {},
7999 1 => {
8000 // Alignment added one padding byte, which the format requires to have value '\n'.
8001 slice[slice.len - 1] = '\n';
8002 },
8003 else => unreachable, // node size should agree with the value we wrote into `ar_hdr.ar_size`
8004 }
8005
7836 var fr = file.reader(io, &.{});8006 var fr = file.reader(io, &.{});
7837 var nw: MappedFile.Node.Writer = undefined;8007 var w: Io.Writer = .fixed(slice[@sizeOf(std.elf.ar_hdr)..]);
7838 ii.node(elf).writer(&elf.mf, gpa, &nw);8008 const n_bytes_read = w.sendFileAll(&fr, .limited(input_size)) catch |err| switch (err) {
7839 defer nw.deinit();
7840 const size = nw.interface.buffer.len - @sizeOf(std.elf.ar_hdr);
7841 const n_bytes = nw.interface.sendFileAll(&fr, .limited(size)) catch |err| switch (err) {
7842 error.ReadFailed => return diags.fail("failed to read input \"{f}{f}\": {t}", .{8009 error.ReadFailed => return diags.fail("failed to read input \"{f}{f}\": {t}", .{
7843 path.fmtEscapeString(),8010 path.fmtEscapeString(),
7844 fmtMemberString(ii.member(elf)),8011 fmtMemberString(ii.member(elf)),
7845 fr.err orelse (fr.seek_err orelse fr.size_err.?),8012 fr.err orelse (fr.seek_err orelse fr.size_err.?),
7846 }),8013 }),
7847 error.WriteFailed => return nw.err.?,8014 error.WriteFailed => unreachable, // `.limited(input_size)` prevents us writing too many bytes
7848 };8015 };
7849 if (n_bytes + 1 < size) return diags.fail("failed to read input \"{f}{f}\": unexpected eof", .{8016 if (n_bytes_read != input_size) {
7850 path.fmtEscapeString(),8017 return diags.fail("failed to load input \"{f}{f}\": file truncated during compilation", .{
7851 fmtMemberString(ii.member(elf)),8018 path.fmtEscapeString(),
7852 });8019 fmtMemberString(ii.member(elf)),
8020 });
8021 }
7853}8022}
78548023
7855fn flushInputSection(elf: *Elf, isi: InputSection.Index) Error!void {8024fn flushInputSection(elf: *Elf, isi: InputSection.Index) Error!void {
...@@ -7931,12 +8100,18 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -7931,12 +8100,18 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
7931 const trace = tracy.trace(@src());8100 const trace = tracy.trace(@src());
7932 defer trace.end();8101 defer trace.end();
79338102
7934 elf.mf.nodes_lock.lock();
7935 defer elf.mf.nodes_lock.unlock();
7936
7937 switch (elf.getNode(ni)) {8103 switch (elf.getNode(ni)) {
7938 .archive, .archive_header => unreachable,8104 .archive => unreachable,
7939 .archive_elf_footer, .elf => {},8105 .archive_header => unreachable,
8106
8107 .archive_input_member,
8108 .archive_elf_member_header,
8109 .elf,
8110 => {
8111 assert(elf.archive != null);
8112 return;
8113 },
8114
7940 .ehdr, .shdr => elf.flushElfOffset(ni),8115 .ehdr, .shdr => elf.flushElfOffset(ni),
7941 .segment => |phndx| {8116 .segment => |phndx| {
7942 elf.flushElfOffset(ni);8117 elf.flushElfOffset(ni);
...@@ -8014,7 +8189,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -8014,7 +8189,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
8014 elf.flushMovedPltSection(.plt_sec, old_addr, addr);8189 elf.flushMovedPltSection(.plt_sec, old_addr, addr);
8015 }8190 }
8016 },8191 },
8017 .input_member => {},
8018 .input_section => |isi| {8192 .input_section => |isi| {
8019 const old_section_addr = isi.ptr(elf).vaddr;8193 const old_section_addr = isi.ptr(elf).vaddr;
8020 const new_section_addr = elf.computeNodeVAddr(ni);8194 const new_section_addr = elf.computeNodeVAddr(ni);
...@@ -8213,24 +8387,23 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo...@@ -8213,24 +8387,23 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
8213 const trace = tracy.trace(@src());8387 const trace = tracy.trace(@src());
8214 defer trace.end();8388 defer trace.end();
82158389
8216 elf.mf.nodes_lock.lock();
8217 defer elf.mf.nodes_lock.unlock();
8218
8219 _, const size = ni.location(&elf.mf).resolve(&elf.mf);8390 _, const size = ni.location(&elf.mf).resolve(&elf.mf);
8220 switch (elf.getNode(ni)) {8391 switch (elf.getNode(ni)) {
8221 .archive => {8392 .archive, .archive_header => {},
8222 if (ni.last(&elf.mf).unwrap()) |last_ni| {8393 .archive_input_member => unreachable,
8223 if (last_ni.prev(&elf.mf).unwrap()) |prev_ni| {8394 .archive_elf_member_header => unreachable,
8224 if (prev_ni.hasNextMoved(&elf.mf)) return;8395 .elf => if (elf.archive) |*archive| {
8225 }8396 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
8226 const offset, _ = last_ni.location(&elf.mf).resolve(&elf.mf);8397 archive.elf_member_header_ni.slice(&elf.mf),
8227 _ = std.mem.print(&elf.arHdrPtr(last_ni).ar_size, "{d:<10}", .{8398 );
8228 size - offset,8399 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{size})) |size_str| {
8229 }) catch @panic("archive member too large");8400 @memset(member_ar_hdr.ar_size[size_str.len..], ' ');
8401 archive.elf_member_too_big = false;
8402 } else |err| switch (err) {
8403 error.NoSpaceLeft => archive.elf_member_too_big = true,
8230 }8404 }
8231 },8405 },
8232 .archive_header, .elf => {},8406 .ehdr => unreachable,
8233 .ehdr, .archive_elf_footer => unreachable,
8234 .shdr => {},8407 .shdr => {},
8235 .segment => |phndx| switch (elf.phdrSlice()) {8408 .segment => |phndx| switch (elf.phdrSlice()) {
8236 inline else => |phdr| {8409 inline else => |phdr| {
...@@ -8302,7 +8475,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo...@@ -8302,7 +8475,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
8302 }8475 }
8303 },8476 },
8304 },8477 },
8305 .input_member, .input_section, .copied_global, .nav, .uav, .lazy_code, .lazy_const_data => {},8478 .input_section, .copied_global, .nav, .uav, .lazy_code, .lazy_const_data => {},
8306 }8479 }
8307}8480}
83088481
...@@ -8310,12 +8483,11 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!...@@ -8310,12 +8483,11 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!
8310 const trace = tracy.trace(@src());8483 const trace = tracy.trace(@src());
8311 defer trace.end();8484 defer trace.end();
83128485
8313 elf.mf.nodes_lock.lock();
8314 defer elf.mf.nodes_lock.unlock();
8315
8316 switch (elf.getNode(ni)) {8486 switch (elf.getNode(ni)) {
8317 .archive,8487 .archive,
8318 .archive_elf_footer,8488 .archive_input_member,
8489 .archive_elf_member_header,
8490 .elf,
8319 .ehdr,8491 .ehdr,
8320 .shdr,8492 .shdr,
8321 .segment,8493 .segment,
...@@ -8327,54 +8499,32 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!...@@ -8327,54 +8499,32 @@ fn flushNextMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!
8327 .lazy_code,8499 .lazy_code,
8328 .lazy_const_data,8500 .lazy_const_data,
8329 => unreachable,8501 => unreachable,
8330 .archive_header, .elf, .input_member => |_, tag| {8502
8331 const member_offset, const update_size = member_offset: {8503 .archive_header => {
8332 const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);8504 const archive = &elf.archive.?;
8333 break :member_offset switch (tag) {8505
8334 else => unreachable,8506 // Because we can't just throw padding bytes in the middle of an archive file, we need
8335 .archive_header => .{ offset + std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr), true },8507 // the member name string table (the "//" member) to absorb all the padding bytes
8336 .elf, .input_member => .{ offset, !ni.prev(&elf.mf).unwrap().?.hasNextMoved(&elf.mf) },8508 // between it (in the `.archive_header` node) and the first actual member.
8337 };8509 const next_member_ni = ni.next(&elf.mf).unwrap() orelse {
8338 };8510 // I guess there are no link inputs yet? But there will be eventually!
8339 const member_size = if (ni.next(&elf.mf).unwrap()) |next_ni| member_size: {8511 return;
8340 const next_offset, _ = next_ni.location(&elf.mf).resolve(&elf.mf);
8341 const next_member_size = if (next_ni.next(&elf.mf).unwrap()) |next_next_ni| next_member_size: {
8342 const next_next_offset, _ = next_next_ni.location(&elf.mf).resolve(&elf.mf);
8343 const next_member_end = next_next_offset - @sizeOf(std.elf.ar_hdr);
8344 break :next_member_size next_member_end - next_offset;
8345 } else next_member_size: {
8346 _, const parent_size = ni.parent(&elf.mf).unwrap().?.location(&elf.mf).resolve(&elf.mf);
8347 const next_member_end = parent_size;
8348 break :next_member_size next_member_end - next_offset;
8349 };
8350 const ar_hdr = elf.arHdrPtr(next_ni);
8351 var name_buf: [16]u8 = undefined;
8352 _ = std.mem.print(&ar_hdr.ar_name, "{s:<16}", .{
8353 switch (elf.getNode(next_ni)) {
8354 else => unreachable,
8355 .elf => std.mem.print(&name_buf, "{s}_zcu.o/", .{elf.base.comp.root_name}),
8356 .input_member => |ii| std.mem.print(&name_buf, "{s}/", .{
8357 std.fs.path.basename(ii.path(elf).sub_path),
8358 }),
8359 } catch @panic("TODO: long archive member names"),
8360 }) catch @panic("TODO: long archive member names");
8361 ar_hdr.ar_date = "0 ".*;
8362 ar_hdr.ar_uid = "0 ".*;
8363 ar_hdr.ar_gid = "0 ".*;
8364 ar_hdr.ar_mode = "644 ".*;
8365 _ = std.mem.print(&ar_hdr.ar_size, "{d:<10}", .{next_member_size}) catch
8366 @panic("archive member too large");
8367 ar_hdr.ar_fmag = std.elf.ARFMAG.*;
8368 const member_end = next_offset - @sizeOf(std.elf.ar_hdr);
8369 break :member_size member_end - member_offset;
8370 } else member_size: {
8371 _, const parent_size = ni.parent(&elf.mf).unwrap().?.location(&elf.mf).resolve(&elf.mf);
8372 const member_end = parent_size;
8373 break :member_size member_end - member_offset;
8374 };8512 };
8375 if (update_size) _ = std.mem.print(&elf.arHdrPtr(ni).ar_size, "{d:<10}", .{8513 const next_member_offset: u64, _ = next_member_ni.location(&elf.mf).resolve(&elf.mf);
8376 member_size,8514 const strtab_member_offset = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr);
8377 }) catch @panic("archive member too large");8515 assert(Alignment.@"2".check(next_member_offset));
8516 assert(Alignment.@"2".check(strtab_member_offset));
8517 const strtab_size = next_member_offset - strtab_member_offset;
8518
8519 const member_ar_hdr: *std.elf.ar_hdr = @ptrCast(
8520 archive.header_ni.slice(&elf.mf)[std.elf.ARMAG.len..][0..@sizeOf(std.elf.ar_hdr)],
8521 );
8522 if (std.mem.print(&member_ar_hdr.ar_size, "{d}", .{strtab_size})) |size_str| {
8523 @memset(member_ar_hdr.ar_size[size_str.len..], ' ');
8524 archive.strtab_member_too_big = false;
8525 } else |err| switch (err) {
8526 error.NoSpaceLeft => archive.strtab_member_too_big = true,
8527 }
8378 },8528 },
8379 }8529 }
8380}8530}