From ede0017eb9e72cb5a799ad000ebe45cf326524d6 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 15 Aug 2026 08:59:55 +0100 Subject: [PATCH] Elf2: include the ehdr in the rodata segment Although to my knowledge this is not strictly required by the format or by any OS, it is highly conventional, and not doing so can definitely break things in practice---including one of the standard library tests! To achieve this, we add all of the segment nodes *before* the ehdr, initializing rodata as a fixed "header" node in the `.elf` node, and then the `.ehdr` node goes within that rodata segment node. --- src/link/Elf2.zig | 179 +++++++++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 81 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index d6eb9501e3aa7bae9f6e3667e719d78b76d8f6d2..831db4697aeab89e3e9bfcab3dde58fa7e3b4e6e 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -3645,9 +3645,98 @@ fn initHeaders( const entsize: struct { ph: u32, sh: u32 } = switch (class) { .NONE, _ => unreachable, - inline else => |ct_class| entsize: { + inline else => |ct_class| .{ + .ph = @sizeOf(ct_class.ElfN().Phdr), + .sh = @sizeOf(ct_class.ElfN().Shdr), + }, + }; + + // We want to create the segment nodes *before* the ehdr, because the ehdr should go inside of + // the rodata segment. Although to my knowledge neither ELF nor any ELF-based OS strictly + // requires this, it is highly conventional and therefore sometimes relied upon. + if (@"type" != .REL) { + elf.ni.rodata = try elf.mf.addOnlyChildNode(gpa, elf.ni.elf, .{ + // Must be at least `addr_align` for `elf.ni.phdr` to be placed inside this node + .alignment = node_block_align.max(addr_align), + // This node will contain the ehdr, which must be at the start of the ELF file, so this + // node must itself be fixed. + .fixed = true, + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.rodata }); + elf.phdrs.items[phndx.rodata] = elf.ni.rodata; + + elf.ni.phdr = try elf.mf.addOnlyChildNode(gpa, elf.ni.rodata, .{ + .size = @as(u64, phnum) * entsize.ph, + .alignment = addr_align, // keep in sync with `elf.ni.rodata` alignment above + .moved = true, + .resized = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.phdr }); + elf.phdrs.items[phndx.phdr] = elf.ni.phdr; + + elf.ni.text = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ + .alignment = node_block_align, + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.text }); + elf.phdrs.items[phndx.text] = elf.ni.text; + + elf.ni.data = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ + // Must be at least `addr_align` for `elf.ni.data_rel_ro` to be placed inside this node + .alignment = node_block_align.max(addr_align), + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.data }); + elf.phdrs.items[phndx.data] = elf.ni.data; + + if (plt.got_plt == null) { + const plt_ni = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ + .alignment = node_block_align, + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.plt }); + elf.phdrs.items[phndx.plt] = plt_ni; + } + + elf.ni.data_rel_ro = try elf.mf.addOnlyChildNode(gpa, elf.ni.data, .{ + // Must be at least `addr_align` for the `PT_DYNAMIC` node to be placed inside this one + // later (if `have_dynamic_section`). Keep in sync with `elf.ni.data` alignment above. + .alignment = node_block_align.max(addr_align), + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.relro }); + elf.phdrs.items[phndx.relro] = elf.ni.data_rel_ro; + + if (comp.config.any_non_single_threaded) { + elf.ni.tls = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{ + .alignment = node_block_align, + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = phndx.tls }); + elf.phdrs.items[phndx.tls] = elf.ni.tls; + } + + elf.phdrs.items[phndx.gnu_stack] = .none; + } + + switch (class) { + .NONE, _ => unreachable, + inline else => |ct_class| { const ElfN = ct_class.ElfN(); - elf.ni.ehdr = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ + // In loadable modules, the ehdr goes in the rodata segment, as described above. + const parent_ni = switch (@"type") { + .REL => elf.ni.elf, + .DYN, .EXEC => elf.ni.rodata, + }; + elf.ni.ehdr = try elf.mf.addFirstChildNode(gpa, parent_ni, .{ .size = @sizeOf(ElfN.Ehdr), .alignment = addr_align, .fixed = true, @@ -3699,10 +3788,8 @@ fn initHeaders( ehdr.shnum = 1; // Only the null shdr initially---will be incremented by `addSection` ehdr.shstrndx = std.elf.SHN_UNDEF; if (elf.targetEndian() != native_endian) std.mem.byteSwapAllFields(ElfN.Ehdr, ehdr); - - break :entsize .{ .ph = @sizeOf(ElfN.Phdr), .sh = @sizeOf(ElfN.Shdr) }; }, - }; + } elf.ni.shdr = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ .size = 1 * entsize.sh, // as above, only the null shdr initially @@ -3712,75 +3799,6 @@ fn initHeaders( }); elf.nodes.appendAssumeCapacity(.shdr); - if (@"type" != .REL) { - elf.ni.rodata = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ - // Must be at least `addr_align` for `elf.ni.phdr` to be placed inside this node - .alignment = node_block_align.max(addr_align), - .moved = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.rodata }); - elf.phdrs.items[phndx.rodata] = elf.ni.rodata; - - elf.ni.phdr = try elf.mf.addOnlyChildNode(gpa, elf.ni.rodata, .{ - .size = @as(u64, phnum) * entsize.ph, - .alignment = addr_align, // keep in sync with `elf.ni.rodata` alignment above - .moved = true, - .resized = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.phdr }); - elf.phdrs.items[phndx.phdr] = elf.ni.phdr; - - elf.ni.text = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ - .alignment = node_block_align, - .moved = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.text }); - elf.phdrs.items[phndx.text] = elf.ni.text; - - elf.ni.data = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ - // Must be at least `addr_align` for `elf.ni.data_rel_ro` to be placed inside this node - .alignment = node_block_align.max(addr_align), - .moved = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.data }); - elf.phdrs.items[phndx.data] = elf.ni.data; - - if (plt.got_plt == null) { - const plt_ni = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{ - .alignment = node_block_align, - .moved = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.plt }); - elf.phdrs.items[phndx.plt] = plt_ni; - } - - elf.ni.data_rel_ro = try elf.mf.addOnlyChildNode(gpa, elf.ni.data, .{ - // Must be at least `addr_align` for the `PT_DYNAMIC` node to be placed inside this one - // later (if `have_dynamic_section`). Keep in sync with `elf.ni.data` alignment above. - .alignment = node_block_align.max(addr_align), - .moved = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.relro }); - elf.phdrs.items[phndx.relro] = elf.ni.data_rel_ro; - - if (comp.config.any_non_single_threaded) { - elf.ni.tls = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{ - .alignment = node_block_align, - .moved = true, - .bubbles_moved = false, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = phndx.tls }); - elf.phdrs.items[phndx.tls] = elf.ni.tls; - } - - elf.phdrs.items[phndx.gnu_stack] = .none; - } switch (class) { .NONE, _ => unreachable, inline else => |ct_class| { @@ -3818,7 +3836,9 @@ fn initHeaders( // actually `PT_NULL` for now, because we initialize `filesz` and `memsz` to zero. // Any which end up non-empty will have their size populated (and their type set to // `PT_LOAD`) by the segment virtual address space allocation logic. - const phdr: []ElfN.Phdr = @ptrCast(@alignCast(elf.ni.phdr.slice(&elf.mf))); + const phdr: []ElfN.Phdr = @ptrCast(@alignCast( + elf.ni.phdr.slice(&elf.mf)[0 .. phnum * @sizeOf(ElfN.Phdr)], + )); const ph_phdr = &phdr[phndx.phdr]; ph_phdr.* = .{ @@ -4900,14 +4920,11 @@ const PhdrSlice = union(std.elf.CLASS) { }; fn phdrSlice(elf: *Elf) PhdrSlice { assert(elf.ehdrType() != .REL); - const slice = elf.ni.phdr.slice(&elf.mf); return switch (elf.identClass()) { .NONE, _ => unreachable, - inline else => |class| @unionInit( - PhdrSlice, - @tagName(class), - @ptrCast(@alignCast(slice)), - ), + inline else => |class| @unionInit(PhdrSlice, @tagName(class), @ptrCast(@alignCast( + elf.ni.phdr.slice(&elf.mf)[0 .. elf.phdrs.items.len * @sizeOf(class.ElfN().Phdr)], + ))), }; } -- 2.54.0