authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-13 21:08:02+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-16 10:37:19+01:00
log99e54fc4209af72eb34b7d32bc5efd28a2a7b1ba
treec6db30640d3df6812bca14391b7b89fada61f518
parentbd805e82b9984bc388217415d2a814550d4033a8
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: many alignment fixes

Notably, this ensures that loadable segments are well-aligned in all cases, and does so *without* requiring segments to have well-aligned file offsets (which would result in needless bloat in the ELF file). It also fixes a bunch of small alignment bugs across the backend, and adds correct handling for the case of an input section / NAV / UAV forcing a section, and possibly even segment, to increase its alignment. Resolves: https://codeberg.org/ziglang/zig/issues/32135

1 files changed, 353 insertions(+), 197 deletions(-)

src/link/Elf2.zig+353-197
......@@ -539,6 +539,26 @@ const Section = struct {
539539 }
540540 }
541541
542 fn ensureAligned(shndx: Index, elf: *Elf, min_align: std.mem.Alignment) Error!void {
543 switch (elf.shdrPtr(shndx)) {
544 inline else => |shdr| {
545 if (elf.targetLoad(&shdr.addralign) >= min_align.toByteUnits()) {
546 return; // already aligned
547 }
548 elf.targetStore(&shdr.addralign, @intCast(min_align.toByteUnits()));
549 },
550 }
551 const ni = shndx.get(elf).ni;
552 if (min_align.compare(.gt, ni.alignment(&elf.mf))) {
553 try ni.realign(&elf.mf, elf.base.comp.gpa, min_align, .{});
554 }
555 switch (elf.getNode(ni.parent(&elf.mf))) {
556 .elf => {},
557 .segment => |phndx| try elf.ensureSegmentAligned(phndx, min_align),
558 else => unreachable,
559 }
560 }
561
542562 /// Asserts that `rela_shndx` is a `SHT_RELA` section and ensures that its node has enough
543563 /// unused space to hold `n` additional `ElfN.Rela` entries.
544564 fn relaEnsureAdditionalCapacity(rela_shndx: Index, elf: *Elf, n: usize) Error!void {
......@@ -3455,6 +3475,16 @@ fn initHeaders(
34553475 .@"64" => .@"8",
34563476 };
34573477
3478 // Minimum alignment for an arbitrarily-chosen set of "large" nodes in the file (e.g. common
3479 // sections), to allow `MappedFile` to perform operations more efficiently. The downside to
3480 // using `elf.mf.flags.block_size` is that it causes outputs to be potentially unreproducible
3481 // across host filesystems, so in the future we may want to set this to `.@"1"` when using a
3482 // build mode that requires reproducibility.
3483 //
3484 // It can be handy to temporarily set this to `.@"1"` when working on the linker, because it
3485 // prevents alignment bugs from being hidden by your filesystem's block alignment.
3486 const node_block_align: std.mem.Alignment = elf.mf.flags.block_size;
3487
34583488 const plt: PltInfo = .fromMachine(machine);
34593489
34603490 const shnum: u32 = shnum: {
......@@ -3577,7 +3607,7 @@ fn initHeaders(
35773607
35783608 elf.nodes.appendAssumeCapacity(.archive_header);
35793609 elf.ni.elf = try elf.mf.addLastChildNode(gpa, elf.ni.archive, .{
3580 .alignment = elf.mf.flags.block_size.max(.@"2"),
3610 .alignment = node_block_align.max(.@"2"),
35813611 .next_moved = true,
35823612 .bubbles_moved = false,
35833613 .enable_next_moved = true,
......@@ -3648,36 +3678,16 @@ fn initHeaders(
36483678
36493679 elf.ni.shdr = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
36503680 .size = 1 * entsize.sh, // as above, only the null shdr initially
3651 .alignment = elf.mf.flags.block_size,
3681 .alignment = addr_align.max(node_block_align),
36523682 .moved = true,
36533683 .resized = true,
36543684 });
36553685 elf.nodes.appendAssumeCapacity(.shdr);
36563686
3657 const page_align: std.mem.Alignment = .fromByteUnits(switch (machine) {
3658 .AARCH64 => 0x10000,
3659 .LOONGARCH => 0x4000,
3660 .PPC64 => 0x10000,
3661 .RISCV => 0x1000,
3662 .SPARCV9 => 0x100000,
3663 .X86_64 => 0x1000,
3664
3665 //.@"68K" => 0x2000,
3666 //.AMDGPU => 0x10000,
3667 //.ARC_COMPACT2 => 0x2000,
3668 //.AVR => 0x1,
3669 //.BPF => 0x100000,
3670 //.MIPS => 0x10000,
3671 //.MSP430 => 0x4,
3672 //.PPC => 0x10000,
3673 //.QDSP6 => 0x10000,
3674 //.SPARC => 0x10000,
3675 //.SPARC32PLUS => 0x10000,
3676 });
3677
3678 var ph_vaddr: u32 = if (@"type" != .REL) ph_vaddr: {
3687 if (@"type" != .REL) {
36793688 elf.ni.rodata = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3680 .alignment = elf.mf.flags.block_size,
3689 // Must be at least `addr_align` for `elf.ni.phdr` to be placed inside this node
3690 .alignment = node_block_align.max(addr_align),
36813691 .moved = true,
36823692 .bubbles_moved = false,
36833693 });
......@@ -3686,7 +3696,7 @@ fn initHeaders(
36863696
36873697 elf.ni.phdr = try elf.mf.addOnlyChildNode(gpa, elf.ni.rodata, .{
36883698 .size = @as(u64, phnum) * entsize.ph,
3689 .alignment = addr_align,
3699 .alignment = addr_align, // keep in sync with `elf.ni.rodata` alignment above
36903700 .moved = true,
36913701 .resized = true,
36923702 .bubbles_moved = false,
......@@ -3695,7 +3705,7 @@ fn initHeaders(
36953705 elf.phdrs.items[phndx.phdr] = elf.ni.phdr;
36963706
36973707 elf.ni.text = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3698 .alignment = elf.mf.flags.block_size,
3708 .alignment = node_block_align,
36993709 .moved = true,
37003710 .bubbles_moved = false,
37013711 });
......@@ -3703,7 +3713,8 @@ fn initHeaders(
37033713 elf.phdrs.items[phndx.text] = elf.ni.text;
37043714
37053715 elf.ni.data = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3706 .alignment = elf.mf.flags.block_size,
3716 // Must be at least `addr_align` for `elf.ni.data_rel_ro` to be placed inside this node
3717 .alignment = node_block_align.max(addr_align),
37073718 .moved = true,
37083719 .bubbles_moved = false,
37093720 });
......@@ -3711,36 +3722,66 @@ fn initHeaders(
37113722 elf.phdrs.items[phndx.data] = elf.ni.data;
37123723
37133724 elf.ni.data_rel_ro = try elf.mf.addOnlyChildNode(gpa, elf.ni.data, .{
3714 .alignment = elf.mf.flags.block_size,
3725 // Must be at least `addr_align` for the `PT_DYNAMIC` node to be placed inside this one
3726 // later (if `have_dynamic_section`). Keep in sync with `elf.ni.data` alignment above.
3727 .alignment = node_block_align.max(addr_align),
37153728 .moved = true,
37163729 .bubbles_moved = false,
37173730 });
37183731 elf.nodes.appendAssumeCapacity(.{ .segment = phndx.relro });
37193732 elf.phdrs.items[phndx.relro] = elf.ni.data_rel_ro;
37203733
3721 elf.phdrs.items[phndx.gnu_stack] = .none;
3734 if (comp.config.any_non_single_threaded) {
3735 elf.ni.tls = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
3736 .alignment = node_block_align,
3737 .moved = true,
3738 .bubbles_moved = false,
3739 });
3740 elf.nodes.appendAssumeCapacity(.{ .segment = phndx.tls });
3741 elf.phdrs.items[phndx.tls] = elf.ni.tls;
3742 }
37223743
3723 break :ph_vaddr switch (elf.ehdrType()) {
3724 .REL, .DYN => 0,
3725 .EXEC => switch (machine) {
3726 .AARCH64,
3727 => 0x200000,
3728 .LOONGARCH => 0x10000,
3729 .PPC64 => 0x10000000,
3730 .RISCV => 0x10000,
3731 .SPARCV9 => 0x100000,
3732 .X86_64 => 0x200000,
3733 },
3734 };
3735 } else undefined;
3744 elf.phdrs.items[phndx.gnu_stack] = .none;
3745 }
37363746 switch (class) {
37373747 .NONE, _ => unreachable,
37383748 inline else => |ct_class| {
37393749 const ElfN = ct_class.ElfN();
37403750 const target_endian = elf.targetEndian();
37413751
3742 if (@"type" != .REL) {
3752 populate_phdrs: {
3753 // Initially we will give every `PT_LOAD` segment this address. When we re-allocate
3754 // segments in the virtual address space in `flushMoved` and `flushResized`, we will
3755 // move some segments to higher addresses to prevent overlap. This address therefore
3756 // becomes the image's "base address"; i.e. the first `PT_LOAD` segment will start
3757 // at this address. The base address could eventually end up higher than this due to
3758 // how we re-allocate the address space, but never lower.
3759 const base_vaddr: u64 = switch (@"type") {
3760 .REL => break :populate_phdrs,
3761 .DYN => 0,
3762 .EXEC => switch (machine) {
3763 .AARCH64 => 0x200000,
3764 .LOONGARCH => 0x10000,
3765 .PPC64 => 0x10000000,
3766 .RISCV => 0x10000,
3767 .SPARCV9 => 0x100000,
3768 .X86_64 => 0x200000,
3769 },
3770 };
3771
3772 // All `PT_LOAD` segments are given this `.@"align"`. However, to avoid bloating the
3773 // binary, their *nodes* are not aligned to this boundary---ELF only requires that
3774 // ecah segment's address equals its file offset modulo this alignment, not that its
3775 // file offset is actually aligned to this boundary. This property is maintained by
3776 // the segment virtual address space allocation logic.
3777 const page_align = elf.targetPageAlign();
3778
3779 // We will populate elements in this slice (by index). The `PT_LOAD` segments are
3780 // actually `PT_NULL` for now, because we initialize `filesz` and `memsz` to zero.
3781 // Any which end up non-empty will have their size populated (and their type set to
3782 // `PT_LOAD`) by the segment virtual address space allocation logic.
37433783 const phdr: []ElfN.Phdr = @ptrCast(@alignCast(elf.ni.phdr.slice(&elf.mf)));
3784
37443785 const ph_phdr = &phdr[phndx.phdr];
37453786 ph_phdr.* = .{
37463787 .type = .PHDR,
......@@ -3752,7 +3793,6 @@ fn initHeaders(
37523793 .flags = .{ .R = true },
37533794 .@"align" = @intCast(elf.ni.phdr.alignment(&elf.mf).toByteUnits()),
37543795 };
3755 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_phdr);
37563796
37573797 if (maybe_interp) |_| {
37583798 const ph_interp = &phdr[phndx.interp];
......@@ -3766,53 +3806,43 @@ fn initHeaders(
37663806 .flags = .{ .R = true },
37673807 .@"align" = 1,
37683808 };
3769 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_interp);
37703809 }
37713810
3772 _, const rodata_size = elf.ni.rodata.location(&elf.mf).resolve(&elf.mf);
37733811 const ph_rodata = &phdr[phndx.rodata];
37743812 ph_rodata.* = .{
3775 .type = if (rodata_size == 0) .NULL else .LOAD,
3813 .type = .NULL,
37763814 .offset = 0,
3777 .vaddr = ph_vaddr,
3778 .paddr = ph_vaddr,
3779 .filesz = @intCast(rodata_size),
3780 .memsz = @intCast(rodata_size),
3815 .vaddr = @intCast(base_vaddr),
3816 .paddr = @intCast(base_vaddr),
3817 .filesz = 0,
3818 .memsz = 0,
37813819 .flags = .{ .R = true },
3782 .@"align" = @intCast(elf.ni.rodata.alignment(&elf.mf).max(page_align).toByteUnits()),
3820 .@"align" = @intCast(page_align.toByteUnits()),
37833821 };
3784 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_rodata);
3785 ph_vaddr += @intCast(rodata_size);
37863822
3787 _, const text_size = elf.ni.text.location(&elf.mf).resolve(&elf.mf);
37883823 const ph_text = &phdr[phndx.text];
37893824 ph_text.* = .{
3790 .type = if (text_size == 0) .NULL else .LOAD,
3825 .type = .NULL,
37913826 .offset = 0,
3792 .vaddr = ph_vaddr,
3793 .paddr = ph_vaddr,
3794 .filesz = @intCast(text_size),
3795 .memsz = @intCast(text_size),
3827 .vaddr = @intCast(base_vaddr),
3828 .paddr = @intCast(base_vaddr),
3829 .filesz = 0,
3830 .memsz = 0,
37963831 .flags = .{ .R = true, .X = true },
3797 .@"align" = @intCast(elf.ni.text.alignment(&elf.mf).max(page_align).toByteUnits()),
3832 .@"align" = @intCast(page_align.toByteUnits()),
37983833 };
3799 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_text);
3800 ph_vaddr += @intCast(text_size);
38013834
3802 _, const data_size = elf.ni.data.location(&elf.mf).resolve(&elf.mf);
38033835 const ph_data = &phdr[phndx.data];
38043836 ph_data.* = .{
3805 .type = if (data_size == 0) .NULL else .LOAD,
3837 .type = .NULL,
38063838 .offset = 0,
3807 .vaddr = ph_vaddr,
3808 .paddr = ph_vaddr,
3809 .filesz = @intCast(data_size),
3810 .memsz = @intCast(data_size),
3839 .vaddr = @intCast(base_vaddr),
3840 .paddr = @intCast(base_vaddr),
3841 .filesz = 0,
3842 .memsz = 0,
38113843 .flags = .{ .R = true, .W = true },
3812 .@"align" = @intCast(elf.ni.data.alignment(&elf.mf).max(page_align).toByteUnits()),
3844 .@"align" = @intCast(page_align.toByteUnits()),
38133845 };
3814 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_data);
3815 ph_vaddr += @intCast(data_size);
38163846
38173847 if (comp.config.any_non_single_threaded) {
38183848 const ph_tls = &phdr[phndx.tls];
......@@ -3824,9 +3854,8 @@ fn initHeaders(
38243854 .filesz = 0,
38253855 .memsz = 0,
38263856 .flags = .{ .R = true },
3827 .@"align" = @intCast(elf.mf.flags.block_size.toByteUnits()),
3857 .@"align" = @intCast(elf.ni.tls.alignment(&elf.mf).toByteUnits()),
38283858 };
3829 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_tls);
38303859 }
38313860
38323861 if (have_dynamic_section) {
......@@ -3841,7 +3870,6 @@ fn initHeaders(
38413870 .flags = .{ .R = true, .W = true },
38423871 .@"align" = @intCast(addr_align.toByteUnits()),
38433872 };
3844 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_dynamic);
38453873 }
38463874
38473875 const ph_relro = &phdr[phndx.relro];
......@@ -3853,9 +3881,8 @@ fn initHeaders(
38533881 .filesz = 0,
38543882 .memsz = 0,
38553883 .flags = .{ .R = true },
3856 .@"align" = @intCast(elf.mf.flags.block_size.toByteUnits()),
3884 .@"align" = @intCast(elf.ni.data_rel_ro.alignment(&elf.mf).toByteUnits()),
38573885 };
3858 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_relro);
38593886
38603887 const ph_gnu_stack = &phdr[phndx.gnu_stack];
38613888 ph_gnu_stack.* = .{
......@@ -3868,7 +3895,10 @@ fn initHeaders(
38683895 .flags = .{ .R = true, .W = true },
38693896 .@"align" = 1,
38703897 };
3871 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_gnu_stack);
3898
3899 if (target_endian != std.lang.Endian.native) {
3900 std.mem.byteSwapAllElements(ElfN.Phdr, phdr);
3901 }
38723902 }
38733903
38743904 const sh_undef: *ElfN.Shdr = @ptrCast(@alignCast(elf.ni.shdr.slice(&elf.mf)));
......@@ -3896,7 +3926,7 @@ fn initHeaders(
38963926 .size = @sizeOf(ElfN.Sym) * 1,
38973927 .addralign = addr_align,
38983928 .entsize = @sizeOf(ElfN.Sym),
3899 .node_align = elf.mf.flags.block_size,
3929 .node_align = node_block_align,
39003930 .info = 1, // index of first non-local symbol
39013931 }));
39023932 const symtab_null = @field(elf.symPtr(.null), @tagName(ct_class));
......@@ -3918,7 +3948,7 @@ fn initHeaders(
39183948 .type = .STRTAB,
39193949 .size = 1,
39203950 .entsize = 1,
3921 .node_align = elf.mf.flags.block_size,
3951 .node_align = node_block_align,
39223952 }));
39233953 Section.Index.get(.shstrtab, elf).ni.slice(&elf.mf)[0] = 0;
39243954
......@@ -3930,7 +3960,7 @@ fn initHeaders(
39303960 .type = .STRTAB,
39313961 .size = 1,
39323962 .entsize = 1,
3933 .node_align = elf.mf.flags.block_size,
3963 .node_align = node_block_align,
39343964 }));
39353965 Section.Index.get(.strtab, elf).ni.slice(&elf.mf)[0] = 0;
39363966 switch (elf.shdrPtr(.symtab)) {
......@@ -3940,22 +3970,22 @@ fn initHeaders(
39403970 assert(.rodata == try elf.addSection(elf.ni.rodata, .{
39413971 .name = ".rodata",
39423972 .flags = .{ .ALLOC = true },
3943 .addralign = elf.mf.flags.block_size,
3973 .node_align = node_block_align,
39443974 }));
39453975 assert(.text == try elf.addSection(elf.ni.text, .{
39463976 .name = ".text",
39473977 .flags = .{ .ALLOC = true, .EXECINSTR = true },
3948 .addralign = elf.mf.flags.block_size,
3978 .node_align = node_block_align,
39493979 }));
39503980 assert(.data == try elf.addSection(elf.ni.data, .{
39513981 .name = ".data",
39523982 .flags = .{ .WRITE = true, .ALLOC = true },
3953 .addralign = elf.mf.flags.block_size,
3983 .node_align = node_block_align,
39543984 }));
39553985 assert(.data_rel_ro == try elf.addSection(elf.ni.data_rel_ro, .{
39563986 .name = ".data.rel.ro",
39573987 .flags = .{ .WRITE = true, .ALLOC = true },
3958 .addralign = elf.mf.flags.block_size,
3988 .node_align = node_block_align,
39593989 }));
39603990 if (@"type" != .REL) {
39613991 elf.shndx.got = try elf.addSection(elf.ni.data_rel_ro, .{
......@@ -3992,13 +4022,13 @@ fn initHeaders(
39924022 },
39934023 .size = plt.entry_size * plt.header_entries,
39944024 .addralign = plt.@"align",
3995 .node_align = elf.mf.flags.block_size,
4025 .node_align = node_block_align,
39964026 });
39974027 if (plt.plt_sec != null) elf.shndx.plt_sec = try elf.addSection(elf.ni.text, .{
39984028 .name = ".plt.sec",
39994029 .flags = .{ .ALLOC = true, .EXECINSTR = true },
40004030 .addralign = plt.@"align",
4001 .node_align = elf.mf.flags.block_size,
4031 .node_align = node_block_align,
40024032 });
40034033 if (maybe_interp) |interp| {
40044034 const interp_ni = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
......@@ -4021,6 +4051,7 @@ fn initHeaders(
40214051 sec_interp[interp.len] = 0;
40224052 }
40234053 if (have_dynamic_section) {
4054 assert(elf.ni.data_rel_ro.alignment(&elf.mf).compare(.gte, addr_align));
40244055 const dynamic_ni = try elf.mf.addLastChildNode(gpa, elf.ni.data_rel_ro, .{
40254056 .alignment = addr_align,
40264057 .moved = true,
......@@ -4035,7 +4066,7 @@ fn initHeaders(
40354066 .flags = .{ .ALLOC = true },
40364067 .size = 1,
40374068 .entsize = 1,
4038 .node_align = elf.mf.flags.block_size,
4069 .node_align = node_block_align,
40394070 });
40404071 dynstr_shndx.get(elf).ni.slice(&elf.mf)[0] = 0;
40414072 elf.shndx.dynstr = dynstr_shndx;
......@@ -4053,7 +4084,7 @@ fn initHeaders(
40534084 .info = 1,
40544085 .addralign = addr_align,
40554086 .entsize = @sizeOf(Sym),
4056 .node_align = elf.mf.flags.block_size,
4087 .node_align = node_block_align,
40574088 });
40584089 const dynsym_null = @field(elf.dynsymPtr(0), @tagName(ct_class));
40594090 dynsym_null.* = .{
......@@ -4081,7 +4112,7 @@ fn initHeaders(
40814112 .link = elf.shndx.dynsym.toSection().?,
40824113 .addralign = addr_align,
40834114 .entsize = rela_size,
4084 .node_align = elf.mf.flags.block_size,
4115 .node_align = node_block_align,
40854116 });
40864117 elf.shndx.rela_plt = try elf.addSection(elf.ni.rodata, .{
40874118 .name = ".rela.plt",
......@@ -4091,7 +4122,7 @@ fn initHeaders(
40914122 .info = (if (plt.got_plt != null) elf.shndx.got_plt else elf.shndx.plt).toSection().?,
40924123 .addralign = addr_align,
40934124 .entsize = rela_size,
4094 .node_align = elf.mf.flags.block_size,
4125 .node_align = node_block_align,
40954126 });
40964127 elf.shndx.dynamic = try elf.addSection(dynamic_ni, .{
40974128 .name = ".dynamic",
......@@ -4198,15 +4229,6 @@ fn initHeaders(
41984229 .SPARCV9 => {},
41994230 }
42004231 }
4201 if (comp.config.any_non_single_threaded) {
4202 elf.ni.tls = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
4203 .alignment = elf.mf.flags.block_size,
4204 .moved = true,
4205 .bubbles_moved = false,
4206 });
4207 elf.nodes.appendAssumeCapacity(.{ .segment = phndx.tls });
4208 elf.phdrs.items[phndx.tls] = elf.ni.tls;
4209 }
42104232
42114233 // Populate reserved GOT words.
42124234 switch (machine) {
......@@ -4382,7 +4404,7 @@ fn initHeaders(
43824404 if (comp.config.any_non_single_threaded) elf.shndx.tdata = try elf.addSection(elf.ni.tls, .{
43834405 .name = ".tdata",
43844406 .flags = .{ .WRITE = true, .ALLOC = true, .TLS = true },
4385 .addralign = elf.mf.flags.block_size,
4407 .node_align = node_block_align,
43864408 });
43874409
43884410 assert(elf.nodes.len == expected_nodes_len);
......@@ -4648,6 +4670,28 @@ fn ehdrType(elf: *const Elf) EhdrType {
46484670fn targetPtrSize(elf: *const Elf) u8 {
46494671 return elf.identClass().size();
46504672}
4673fn targetPageAlign(elf: *const Elf) std.mem.Alignment {
4674 return .fromByteUnits(switch (elf.ehdrMachine()) {
4675 .AARCH64 => 0x10000,
4676 .LOONGARCH => 0x4000,
4677 .PPC64 => 0x10000,
4678 .RISCV => 0x1000,
4679 .SPARCV9 => 0x100000,
4680 .X86_64 => 0x1000,
4681
4682 //.@"68K" => 0x2000,
4683 //.AMDGPU => 0x10000,
4684 //.ARC_COMPACT2 => 0x2000,
4685 //.AVR => 0x1,
4686 //.BPF => 0x100000,
4687 //.MIPS => 0x10000,
4688 //.MSP430 => 0x4,
4689 //.PPC => 0x10000,
4690 //.QDSP6 => 0x10000,
4691 //.SPARC => 0x10000,
4692 //.SPARC32PLUS => 0x10000,
4693 });
4694}
46514695fn targetEndian(elf: *const Elf) std.lang.Endian {
46524696 const ident_data: std.elf.DATA = @fromBackingInt(elf.ni.elf.sliceConst(&elf.mf)[std.elf.EI.DATA]);
46534697 return ident_data.endian();
......@@ -4790,7 +4834,9 @@ fn shdrPtr(elf: *Elf, shndx: Section.Index) ShdrPtr {
47904834 switch (elf.identClass()) {
47914835 .NONE, _ => unreachable,
47924836 inline else => |class| {
4793 const shdr_slice: []class.ElfN().Shdr = @ptrCast(@alignCast(raw_slice));
4837 const shdr_slice: []class.ElfN().Shdr = @ptrCast(@alignCast(
4838 raw_slice[0 .. elf.shdrs.items.len * @sizeOf(class.ElfN().Shdr)],
4839 ));
47944840 const shdr_ptr = &shdr_slice[@backingInt(shndx)];
47954841 return @unionInit(ShdrPtr, @tagName(class), shdr_ptr);
47964842 },
......@@ -4845,7 +4891,6 @@ fn navType(elf: *const Elf, nav_resolved: InternPool.Nav.Resolved) std.elf.STT {
48454891fn mapInputSection(elf: *Elf, opts: struct {
48464892 name: []const u8,
48474893 flags: std.elf.SHF,
4848 addralign: std.elf.Xword,
48494894 entsize: std.elf.Xword,
48504895}) (Error || error{
48514896 UnsupportedSectionFlags,
......@@ -4917,16 +4962,12 @@ fn mapInputSection(elf: *Elf, opts: struct {
49174962 flags.COMPRESSED = false;
49184963 break :flags flags;
49194964 },
4920 .node_align = .fromByteUnits(std.math.ceilPowerOfTwoAssert(
4921 usize,
4922 @intCast(@max(opts.addralign, 1)),
4923 )),
49244965 .entsize = std.math.lossyCast(u32, opts.entsize),
49254966 });
49264967 };
4927 // Validate that the input is compatible with this section...
49284968 switch (elf.shdrPtr(existing_shndx)) {
49294969 inline else => |shdr| {
4970 // Validate that the input is compatible with this section
49304971 const cur_flags = elf.targetLoad(&shdr.flags).shf;
49314972 if (cur_flags.EXECINSTR != opts.flags.EXECINSTR or
49324973 cur_flags.WRITE != opts.flags.WRITE or
......@@ -4939,20 +4980,8 @@ fn mapInputSection(elf: *Elf, opts: struct {
49394980 .NULL, .PROGBITS => {},
49404981 else => return error.SectionTypeConflict,
49414982 }
4942 },
4943 }
4944 // ...then realign the section's node if necessary...
4945 if (opts.addralign > existing_shndx.get(elf).ni.alignment(&elf.mf).toByteUnits()) {
4946 const new_alignment: std.mem.Alignment = .fromByteUnits(
4947 std.math.ceilPowerOfTwoAssert(usize, @intCast(opts.addralign)),
4948 );
4949 try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment, .{});
4950 }
4951 // ...and update the shdr as needed.
4952 switch (elf.shdrPtr(existing_shndx)) {
4953 inline else => |shdr| {
4954 // Combine the section flags.
4955 const cur_flags = elf.targetLoad(&shdr.flags).shf;
4983
4984 // All okay, combine the section flags
49564985 elf.targetStore(&shdr.flags, .{ .shf = .{
49574986 .EXECINSTR = cur_flags.EXECINSTR,
49584987 .WRITE = cur_flags.WRITE,
......@@ -4961,11 +4990,6 @@ fn mapInputSection(elf: *Elf, opts: struct {
49614990 .STRINGS = cur_flags.STRINGS and opts.flags.STRINGS,
49624991 .MERGE = cur_flags.MERGE and opts.flags.MERGE,
49634992 } });
4964 // Increase addralign to the maximum of the current value and the new value---the node
4965 // alignment was already increased above.
4966 if (opts.addralign > elf.targetLoad(&shdr.addralign)) {
4967 elf.targetStore(&shdr.addralign, @intCast(opts.addralign));
4968 }
49694993 },
49704994 }
49714995 return existing_shndx;
......@@ -4993,7 +5017,6 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) Error!Node
49935017 .TLS = elf.base.comp.config.any_non_single_threaded and
49945018 nav.resolved.?.@"threadlocal",
49955019 },
4996 .addralign = 1,
49975020 .entsize = 0,
49985021 })) |shndx| {
49995022 break :section shndx;
......@@ -5039,6 +5062,7 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) Error!Node
50395062 else => |a| a,
50405063 },
50415064 };
5065 try shndx.ensureAligned(elf, alignment.toStdMem());
50425066 const node = try elf.mf.addLastChildNode(gpa, shndx.get(elf).ni, .{
50435067 .alignment = alignment.toStdMem(),
50445068 });
......@@ -5082,6 +5106,7 @@ fn uavMapIndex(
50825106 const umi: Node.UavMapIndex = @fromBackingInt(@intCast(uav_gop.index));
50835107 if (!uav_gop.found_existing) {
50845108 const shndx: Section.Index = .data_rel_ro; // TODO: it would be better to use `.rodata` if the UAV value doesn't have relocs
5109 try shndx.ensureAligned(elf, resolved_align.toStdMem());
50855110 const node = try elf.mf.addLastChildNode(gpa, shndx.get(elf).ni, .{
50865111 .moved = true, // see assert at end of `genUav`
50875112 .alignment = resolved_align.toStdMem(),
......@@ -5108,6 +5133,8 @@ fn uavMapIndex(
51085133 elf.pending_uavs.appendAssumeCapacity(umi);
51095134 } else {
51105135 const node = uav_gop.value_ptr.lsi.index().ptr(elf).node;
5136 const shndx = elf.getNode(node.parent(&elf.mf)).section;
5137 try shndx.ensureAligned(elf, resolved_align.toStdMem());
51115138 if (resolved_align.toStdMem().order(node.alignment(&elf.mf)).compare(.gt)) {
51125139 try node.realign(&elf.mf, gpa, resolved_align.toStdMem(), .{});
51135140 }
......@@ -5408,7 +5435,6 @@ fn loadObject(
54085435 const shndx = elf.mapInputSection(.{
54095436 .name = name,
54105437 .flags = section.shdr.flags.shf,
5411 .addralign = section.shdr.addralign,
54125438 .entsize = section.shdr.entsize,
54135439 }) catch |err| switch (err) {
54145440 error.StripSection => continue,
......@@ -5507,12 +5533,13 @@ fn loadObject(
55075533 .node_fixed = true,
55085534 },
55095535 };
5536 const need_align: std.mem.Alignment = .fromByteUnits(
5537 std.math.ceilPowerOfTwoAssert(usize, @intCast(@max(section.shdr.addralign, 1))),
5538 );
5539 try opts.shndx.ensureAligned(elf, need_align);
55105540 const ni = try elf.mf.addLastChildNode(gpa, opts.shndx.get(elf).ni, .{
55115541 .size = section.shdr.size,
5512 .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert(
5513 usize,
5514 @intCast(@max(section.shdr.addralign, 1)),
5515 )),
5542 .alignment = need_align,
55165543 .moved = true, // see assert at end of `flushInputSection`
55175544 .fixed = opts.node_fixed,
55185545 });
......@@ -5882,6 +5909,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars
58825909 if (elf.copied_globals.get(name)) |copied_global| {
58835910 // We have a copy relocation for this global, but the amount of space we
58845911 // reserved for it could be too small or underaligned!
5912 try Section.Index.data.ensureAligned(elf, gop.value_ptr.alignment);
58855913 try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size);
58865914 try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, .{});
58875915 const global_ptr = elf.globalByName(name).?;
......@@ -6264,7 +6292,8 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {
62646292 else => {},
62656293 }
62666294 if (opts.flags.ALLOC and elf.ehdrType() != .REL) {
6267 assert(elf.getNode(segment_ni) == .segment);
6295 const phndx = elf.getNode(segment_ni).segment;
6296 try elf.ensureSegmentAligned(phndx, opts.addralign);
62686297 }
62696298 const gpa = elf.base.comp.gpa;
62706299 try elf.nodes.ensureUnusedCapacity(gpa, 1);
......@@ -7145,6 +7174,8 @@ fn maybeAddCopyRelocation(elf: *Elf, global_name: String(.strtab)) Error!bool {
71457174 if (gop.found_existing) return true;
71467175 errdefer assert(elf.copied_globals.pop().?.key == global_name);
71477176
7177 try Section.Index.data.ensureAligned(elf, dso_global.alignment);
7178
71487179 try elf.nodes.ensureUnusedCapacity(gpa, 1);
71497180 const node = try elf.mf.addLastChildNode(gpa, Section.Index.data.get(elf).ni, .{
71507181 .size = dso_global.size,
......@@ -7777,17 +7808,22 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
77777808 const ph = &phdr[phndx];
77787809 switch (elf.targetLoad(&ph.type)) {
77797810 else => unreachable,
7780 .NULL, .LOAD => return,
7811
7812 .NULL, .LOAD => {
7813 try elf.allocateSegmentLoadAddress(phndx);
7814 },
77817815
77827816 .DYNAMIC,
77837817 .INTERP,
77847818 .PHDR,
77857819 .TLS,
77867820 .GNU_RELRO,
7787 => {},
7821 => {
7822 const new_vaddr = elf.computeNodeVAddr(ni);
7823 elf.targetStore(&ph.vaddr, @intCast(new_vaddr));
7824 elf.targetStore(&ph.paddr, @intCast(new_vaddr));
7825 },
77887826 }
7789 elf.targetStore(&ph.vaddr, @intCast(elf.computeNodeVAddr(ni)));
7790 ph.paddr = ph.vaddr;
77917827 },
77927828 }
77937829 },
......@@ -7928,6 +7964,114 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
79287964 try ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
79297965}
79307966
7967/// Given the index of a `PT_LOAD`/`PT_NULL` segment, assumes that the phdr's `offset` and `filesz`
7968/// have been updated as needed by the caller, and updates the `@"align"`, `vaddr`, `paddr`, and
7969/// `memsz` fields of the segment, in order to place it at a valid virtual address.
7970///
7971/// TODO: this function is currently a source of non-determinism in the linker, because handling the
7972/// moving or resizing of a segment could reorder them and thereby affect how we handle *future*
7973/// changes to segments.
7974fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Error!void {
7975 const segment_ni = elf.phdrs.items[orig_phndx];
7976 assert(elf.getNode(segment_ni).segment == orig_phndx);
7977 const page_align = elf.targetPageAlign();
7978 const node_align = segment_ni.alignment(&elf.mf);
7979 const ph_align = page_align.max(node_align);
7980 switch (elf.phdrSlice()) {
7981 inline else => |phdr| {
7982 const offset = elf.targetLoad(&phdr[orig_phndx].offset);
7983 const size = elf.targetLoad(&phdr[orig_phndx].filesz);
7984
7985 if (size == 0) {
7986 assert(elf.targetLoad(&phdr[orig_phndx].type) == .NULL);
7987 } else {
7988 assert(elf.targetLoad(&phdr[orig_phndx].type) == .LOAD);
7989 }
7990
7991 elf.targetStore(&phdr[orig_phndx].memsz, size);
7992 elf.targetStore(&phdr[orig_phndx].@"align", @intCast(ph_align.toByteUnits()));
7993
7994 const orig_vaddr = elf.targetLoad(&phdr[orig_phndx].vaddr);
7995 assert(elf.targetLoad(&phdr[orig_phndx].paddr) == orig_vaddr);
7996
7997 var vaddr: u64 = orig_vaddr;
7998
7999 // First, we will shift the virtual address as needed in order to maintain the required
8000 // property that vaddr is congruent to offset modulo the phdr alignment.
8001 {
8002 // Compute the candidate address by undoing the current offset and then re-offsetting
8003 vaddr = std.mem.alignBackward(u64, vaddr, ph_align.toByteUnits()) + offset % ph_align.toByteUnits();
8004 // If `node_align` is greater than `page_align`, the address we just set might be in
8005 // the previous segment. The first page we "own" is the one in which the old vaddr
8006 // resides, so check against that.
8007 const first_good_vaddr = std.mem.alignBackward(u64, orig_vaddr, page_align.toByteUnits());
8008 if (vaddr < first_good_vaddr) {
8009 // Yep, we crossed into the previous segment's pages, so correct for that by
8010 // offsetting our address by another `ph_align`.
8011 vaddr += ph_align.toByteUnits();
8012 assert(vaddr >= first_good_vaddr);
8013 }
8014 }
8015
8016 // If our size has changed, or if the address shift above caused our "end" address to
8017 // cross a page boundary, then we might be overlapping with the next segment's pages. In
8018 // that case, we will jump past that segment and give ourselves a new address after it.
8019 // We'll need to repeat this for every loadable phdr after us, until we're no longer
8020 // overlapping anything.
8021 var phndx = orig_phndx;
8022 for (phdr[orig_phndx + 1 ..], orig_phndx + 1..) |*next_ph, next_phndx| {
8023 switch (elf.targetLoad(&next_ph.type)) {
8024 .NULL, .LOAD => {},
8025 else => {
8026 // All loadable segments have contiguous indices, so this indicates we have
8027 // become the last loadable segment, meaning we definitely don't overlap any
8028 // other loadable segment.
8029 break;
8030 },
8031 }
8032
8033 const next_vaddr = elf.targetLoad(&next_ph.vaddr);
8034 // Find the first virtual address which the next phdr "owns" by aligning its vaddr
8035 // backwards to the start of the page.
8036 const next_page_vaddr = std.mem.alignBackward(u64, next_vaddr, page_align.toByteUnits());
8037
8038 // If we're at the same vaddr we started at, then all we're worried about is the
8039 // segment fitting here. However, if we've already changed our virtual address, then
8040 // we might as well try to reserve a bit *more* virtual address space while we're at
8041 // it, because changing virtual address is quite disruptive (we need to re-flush a
8042 // lot of stuff!) and giving ourselves more space will make it less likely to happen
8043 // again.
8044 const target_size = if (vaddr == orig_vaddr) size else size * 4;
8045 if (vaddr + target_size <= next_page_vaddr) {
8046 break; // hooray, we fit here!
8047 }
8048
8049 // We don't fit here, so shift ourselves forward (i.e. swap with `next_phndx`). But
8050 // first we need to adjust `vaddr` to come after it.
8051 const next_size = elf.targetLoad(&next_ph.memsz);
8052 // Instead of putting ourselves right after `next_ph`, we'll go a bit later in the
8053 // address space so that `next_ph` has address space to grow into (like above).
8054 vaddr = ph_align.forward(next_vaddr + next_size * 4) + offset % ph_align.toByteUnits();
8055
8056 // Now just swap the phdrs and update our `phndx`.
8057 std.mem.swap(@TypeOf(next_ph.*), &phdr[phndx], next_ph);
8058 const next_ni = elf.phdrs.items[next_phndx];
8059 elf.phdrs.items[phndx] = next_ni;
8060 elf.nodes.items(.data)[@backingInt(next_ni)] = .{ .segment = phndx };
8061 elf.phdrs.items[next_phndx] = segment_ni;
8062 elf.nodes.items(.data)[@backingInt(segment_ni)] = .{ .segment = @intCast(next_phndx) };
8063 phndx = @intCast(next_phndx);
8064 }
8065
8066 if (vaddr != orig_vaddr) {
8067 elf.targetStore(&phdr[phndx].vaddr, @intCast(vaddr));
8068 elf.targetStore(&phdr[phndx].paddr, @intCast(vaddr));
8069 try segment_ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
8070 }
8071 },
8072 }
8073}
8074
79318075fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void {
79328076 const trace = tracy.trace(@src());
79338077 defer trace.end();
......@@ -7955,68 +8099,40 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
79558099 assert(elf.phdrs.items[phndx] == ni);
79568100 const ph = &phdr[phndx];
79578101 elf.targetStore(&ph.filesz, @intCast(size));
7958 if (size > elf.targetLoad(&ph.memsz)) {
7959 switch (elf.targetLoad(&ph.type)) {
7960 else => unreachable,
7961 .NULL => if (size > 0) elf.targetStore(&ph.type, .LOAD),
7962 .LOAD => if (size == 0) elf.targetStore(&ph.type, .NULL),
7963 .DYNAMIC, .INTERP, .PHDR, std.elf.PT.GNU_RELRO => {
7964 elf.targetStore(&ph.memsz, @intCast(size));
7965 return;
7966 },
7967 .TLS => {
7968 elf.targetStore(&ph.memsz, @intCast(size));
7969 // TPOFF relocations care about the size of the TLS segment. Re-apply
7970 // those, and also update any GOT entries from GOTTPOFF relocations.
7971 for (elf.tls_size_symbol_relocs.keys()) |reloc| {
7972 reloc.get(elf).apply(elf);
7973 }
7974 for (elf.got.keys(), 0..) |got_key, got_index| {
7975 switch (got_key) {
7976 .reserved,
7977 .symbol,
7978 .tlsld0,
7979 .tlsld1,
7980 .tlsgd0,
7981 .tlsgd1,
7982 => {
7983 @branchHint(.likely);
7984 continue;
7985 },
8102 switch (elf.targetLoad(&ph.type)) {
8103 else => unreachable,
8104 .NULL, .LOAD => {
8105 elf.targetStore(&ph.type, if (size > 0) .LOAD else .NULL);
8106 try elf.allocateSegmentLoadAddress(phndx);
8107 },
8108 .DYNAMIC, .INTERP, .PHDR, std.elf.PT.GNU_RELRO => {
8109 elf.targetStore(&ph.memsz, @intCast(size));
8110 },
8111 .TLS => {
8112 elf.targetStore(&ph.memsz, @intCast(size));
8113 // TPOFF relocations care about the size of the TLS segment. Re-apply
8114 // those, and also update any GOT entries from GOTTPOFF relocations.
8115 for (elf.tls_size_symbol_relocs.keys()) |reloc| {
8116 reloc.get(elf).apply(elf);
8117 }
8118 for (elf.got.keys(), 0..) |got_key, got_index| {
8119 switch (got_key) {
8120 .reserved,
8121 .symbol,
8122 .tlsld0,
8123 .tlsld1,
8124 .tlsgd0,
8125 .tlsgd1,
8126 => {
8127 @branchHint(.likely);
8128 continue;
8129 },
79868130
7987 .tpoff => elf.updateGotEntry(got_index),
7988 }
8131 .tpoff => elf.updateGotEntry(got_index),
79898132 }
7990 return ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
7991 },
7992 }
7993 const memsz = ni.alignment(&elf.mf).forward(@intCast(size * 4));
7994 elf.targetStore(&ph.memsz, @intCast(memsz));
7995 var vaddr = elf.targetLoad(&ph.vaddr);
7996 var new_phndx = phndx;
7997 for (phdr[phndx + 1 ..], phndx + 1..) |*next_ph, next_phndx| {
7998 switch (elf.targetLoad(&next_ph.type)) {
7999 else => unreachable,
8000 .NULL, .LOAD => {},
8001 .DYNAMIC, .INTERP, .PHDR, .TLS, .GNU_RELRO, .GNU_STACK => break,
80028133 }
8003 const next_vaddr = elf.targetLoad(&next_ph.vaddr);
8004 if (vaddr + memsz <= next_vaddr) break;
8005 vaddr = next_vaddr + elf.targetLoad(&next_ph.memsz);
8006 std.mem.swap(@TypeOf(ph.*), &phdr[new_phndx], next_ph);
8007 const next_ni = elf.phdrs.items[next_phndx];
8008 elf.phdrs.items[new_phndx] = next_ni;
8009 elf.nodes.items(.data)[@backingInt(next_ni)] = .{ .segment = new_phndx };
8010 new_phndx = @intCast(next_phndx);
8011 }
8012 if (new_phndx != phndx) {
8013 const new_ph = &phdr[new_phndx];
8014 elf.targetStore(&new_ph.vaddr, vaddr);
8015 new_ph.paddr = new_ph.vaddr;
8016 elf.phdrs.items[new_phndx] = ni;
8017 elf.nodes.items(.data)[@backingInt(ni)] = .{ .segment = new_phndx };
80188134 try ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
8019 }
8135 },
80208136 }
80218137 },
80228138 },
......@@ -8693,6 +8809,46 @@ pub fn printNode(
86938809 }
86948810}
86958811
8812fn ensureSegmentAligned(elf: *Elf, start_phndx: u32, min_align: std.mem.Alignment) Error!void {
8813 const gpa = elf.base.comp.gpa;
8814 // We need to loop through parent nodes because segments may be nested (e.g. a PT_TLS segment
8815 // inside a PT_LOAD segment).
8816 var phndx = start_phndx;
8817 while (true) {
8818 // Align the actual node
8819 const seg_ni = elf.phdrs.items[phndx];
8820 if (min_align.compare(.gt, seg_ni.alignment(&elf.mf))) {
8821 try seg_ni.realign(&elf.mf, gpa, min_align, .{});
8822 }
8823 // Update the phdr `@"align"` field if necessary
8824 switch (elf.phdrSlice()) {
8825 inline else => |phdr| switch (elf.targetLoad(&phdr[phndx].type)) {
8826 .NULL, .LOAD => {
8827 // The `@"align"` field is managed by `allocateSegmentLoadAddress`.
8828 //
8829 // It's very likely that the node was moved and/or resized when we realigned it
8830 // just above, but it is possible that it was not moved *but* still has an
8831 // unaligned virtual address. In that case, we need to ensure the segment's
8832 // virtual address range will be recomputed.
8833 if (!min_align.check(elf.targetLoad(&phdr[phndx].vaddr))) {
8834 try seg_ni.moved(gpa, &elf.mf);
8835 }
8836 },
8837 else => elf.targetStore(&phdr[phndx].@"align", @intCast(@max(
8838 elf.targetLoad(&phdr[phndx].@"align"),
8839 min_align.toByteUnits(),
8840 ))),
8841 },
8842 }
8843 // Continue on to the parent segment, if any
8844 switch (elf.getNode(seg_ni.parent(&elf.mf))) {
8845 .segment => |parent_phndx| phndx = parent_phndx,
8846 .elf => return,
8847 else => unreachable,
8848 }
8849 }
8850}
8851
86968852/// Must be called deterministically after any call to `MappedFile.Node.Index.resize`
86978853/// (of `elf.ni.elf` or one of its children) before any possible calls to `idle`.
86988854fn ensureElfNodeSize(elf: *Elf) MappedFile.Error!void {