commit f801995be40bbf10268c0fa7c13f255cd36b14c0 (tree)
parent 2ec50b7e3d54a55860986110e096b784ed15827f
Author: Matthew Lugg <mlugg@mlugg.co.uk>
Date: Wed, 20 May 2026 12:49:54 +0100
Elf2: basic support for INIT_ARRAY/FINI_ARRAY/PREINIT_ARRAY
Diffstat:
| M | src/link/Elf2.zig | | | 330 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
1 file changed, 284 insertions(+), 46 deletions(-)
diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig
@@ -34,6 +34,10 @@ shndx: struct {
dynstr: Section.Index,
dynamic: Section.Index,
tdata: Section.Index,
+ // These sections are created only as needed, and are initially `.UNDEF`.
+ init_array: Section.Index,
+ fini_array: Section.Index,
+ preinit_array: Section.Index,
},
symtab: std.ArrayList(Symbol),
globals: struct {
@@ -1113,7 +1117,7 @@ fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void
const Symbol = struct {
/// The node which this symbol's value is defined relative to. Possible values are:
/// * `.none` for a SHN_ABS or SHN_UNDEF symbol
- /// * A section (the symbol's value is that section's vaddr)
+ /// * A section (the symbol's value is some vaddr in that section)
/// * An input section (the symbol's value is some vaddr in that input section)
/// * A NAV, UAV, or lazy code/data (the symbol's value is exactly the vaddr of that node)
node: MappedFile.Node.Index,
@@ -1918,6 +1922,9 @@ fn create(
.dynstr = .UNDEF,
.dynamic = .UNDEF,
.tdata = .UNDEF,
+ .init_array = .UNDEF,
+ .fini_array = .UNDEF,
+ .preinit_array = .UNDEF,
},
.symtab = .empty,
.globals = .{
@@ -3109,47 +3116,100 @@ fn loadObject(
defer gpa.free(shstrtab);
try elf.nodes.ensureUnusedCapacity(gpa, ehdr.shnum - 1);
try elf.input_sections.ensureUnusedCapacity(gpa, ehdr.shnum - 1);
- for (sections[1..]) |*section| switch (section.shdr.type) {
- else => {},
- .PROGBITS, .NOBITS => {
- if (section.shdr.name >= shstrtab.len) continue;
- const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0);
- const shndx: Section.Index = elf.namedSection(name) orelse shndx: {
- // TODO: actually generate a .bss section. For now, just throw it into `.data`.
- if (std.mem.eql(u8, name, ".bss") or
- std.mem.startsWith(u8, name, ".bss.")) break :shndx .data;
- if (std.mem.eql(u8, name, ".tbss") or
- std.mem.startsWith(u8, name, ".tbss.")) break :shndx elf.shndx.tdata;
- break :shndx .UNDEF;
- };
- if (shndx == .UNDEF) continue;
- const ni = try elf.mf.addLastChildNode(gpa, shndx.get(elf).ni, .{
- .size = section.shdr.size,
- .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert(
- usize,
- @intCast(@max(section.shdr.addralign, 1)),
- )),
- .moved = true, // see assert at end of `flushInputSection`
- });
- elf.nodes.appendAssumeCapacity(.{
- .input_section = @enumFromInt(elf.input_sections.items.len),
- });
- section.isi = @enumFromInt(elf.input_sections.items.len);
- elf.input_sections.addOneAssumeCapacity().* = .{
- .input = input_index,
- .file_location = .{
- .offset = fl.offset + section.shdr.offset,
- .size = if (section.shdr.type == .NOBITS) 0 else section.shdr.size,
+ for (sections[1..]) |*section| {
+ if (section.shdr.name >= shstrtab.len) continue;
+ const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0);
+ const opts: struct {
+ shndx: Section.Index,
+ has_file_bits: bool,
+ node_fixed: bool,
+ } = switch (section.shdr.type) {
+ else => continue,
+ .PROGBITS => .{
+ .shndx = elf.namedSection(name) orelse continue,
+ .has_file_bits = true,
+ .node_fixed = false,
+ },
+ .NOBITS => .{
+ .shndx = shndx: {
+ // TODO: actually generate a .bss section. For now, just throw it into `.data`.
+ if (std.mem.eql(u8, name, ".bss") or std.mem.startsWith(u8, name, ".bss.")) {
+ break :shndx .data;
+ }
+ if (elf.shndx.tdata != .UNDEF and
+ (std.mem.eql(u8, name, ".tbss") or std.mem.startsWith(u8, name, ".tbss.")))
+ {
+ break :shndx elf.shndx.tdata;
+ }
+ continue;
},
- // The section vaddr is initially 0, because the symbol addresses are
- // zero-based. This will eventually be updated by `flushMoved`.
- .vaddr = 0,
- .node = ni,
- .first_reloc = .none,
- };
- elf.synth_prog_node.increaseEstimatedTotalItems(1);
- },
- };
+ .has_file_bits = false,
+ .node_fixed = false,
+ },
+ inline .INIT_ARRAY, .FINI_ARRAY, .PREINIT_ARRAY => |@"type"| .{
+ .shndx = shndx: {
+ // TODO: the input section name may include a "priority" value between 1
+ // and 65535 which should affect the order we assemble input sections in
+ const init_fini_section_name: []const u8 = switch (@"type") {
+ .INIT_ARRAY => "init_array",
+ .FINI_ARRAY => "fini_array",
+ .PREINIT_ARRAY => "preinit_array",
+ else => comptime unreachable,
+ };
+ const shndx: *Section.Index = &@field(elf.shndx, init_fini_section_name);
+ const need_addralign: u8 = switch (class) {
+ .NONE, _ => unreachable,
+ .@"32" => 4,
+ .@"64" => 8,
+ };
+ if (section.shdr.addralign != need_addralign) {
+ return diags.failParse(path, "bad addralign on {t} shdr", .{@"type"});
+ }
+ if (shndx.* == .UNDEF) {
+ try elf.createInitFiniArraySection(shndx, init_fini_section_name, @"type");
+ }
+ switch (elf.shdrPtr(shndx.*)) {
+ inline else => |shdr| {
+ const old_size = elf.targetLoad(&shdr.size);
+ elf.targetStore(&shdr.size, @intCast(old_size + section.shdr.size));
+ },
+ }
+ try shndx.get(elf).ni.resized(gpa, &elf.mf);
+ break :shndx shndx.*;
+ },
+ .has_file_bits = true,
+ // This node must be fixed to prevent padding from being added between different
+ // INIT_ARRAY/FINI_ARRAY/PREINIT_ARRAY input sections.
+ .node_fixed = true,
+ },
+ };
+ const ni = try elf.mf.addLastChildNode(gpa, opts.shndx.get(elf).ni, .{
+ .size = section.shdr.size,
+ .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert(
+ usize,
+ @intCast(@max(section.shdr.addralign, 1)),
+ )),
+ .moved = true, // see assert at end of `flushInputSection`
+ .fixed = opts.node_fixed,
+ });
+ elf.nodes.appendAssumeCapacity(.{
+ .input_section = @enumFromInt(elf.input_sections.items.len),
+ });
+ section.isi = @enumFromInt(elf.input_sections.items.len);
+ elf.input_sections.addOneAssumeCapacity().* = .{
+ .input = input_index,
+ .file_location = .{
+ .offset = fl.offset + section.shdr.offset,
+ .size = if (opts.has_file_bits) section.shdr.size else 0,
+ },
+ // The section vaddr is initially 0, because the symbol addresses are
+ // zero-based. This will eventually be updated by `flushMoved`.
+ .vaddr = 0,
+ .node = ni,
+ .first_reloc = .none,
+ };
+ elf.synth_prog_node.increaseEstimatedTotalItems(1);
+ }
var symmap: std.ArrayList(Symbol.Id) = .empty;
defer symmap.deinit(gpa);
for (sections[1..], 1..) |*symtab, symtab_shndx| switch (symtab.shdr.type) {
@@ -3450,6 +3510,57 @@ fn checkInputIdent(
);
}
+fn createInitFiniArraySection(
+ elf: *Elf,
+ shndx: *Section.Index,
+ comptime name: []const u8,
+ @"type": std.elf.SHT,
+) !void {
+ assert(shndx.* == .UNDEF);
+ const addr_align: std.mem.Alignment = switch (elf.identClass()) {
+ .NONE, _ => unreachable,
+ .@"32" => .@"4",
+ .@"64" => .@"8",
+ };
+ shndx.* = try elf.addSection(elf.ni.data_rel_ro, .{
+ .name = "." ++ name,
+ .type = @"type",
+ .flags = .{ .WRITE = true, .ALLOC = true },
+ .node_align = addr_align,
+ });
+ try elf.ensureUnusedSymbolCapacity(2, .maybe_global);
+ _ = elf.addGlobalSymbolAssumeCapacity(.{
+ .node = shndx.get(elf).ni,
+ .name = try .string(elf, "__" ++ name ++ "_start"),
+ .value = shndx.vaddr(elf),
+ .size = 0,
+ .type = .NOTYPE,
+ .bind = .strong,
+ .visibility = .HIDDEN,
+ .shndx = shndx.*,
+ }) catch |err| switch (err) {
+ error.MultipleDefinitions => return elf.base.comp.link_diags.fail(
+ "multiple definitions of '{s}'",
+ .{"__" ++ name ++ "_start"},
+ ),
+ };
+ _ = elf.addGlobalSymbolAssumeCapacity(.{
+ .node = shndx.get(elf).ni,
+ .name = try .string(elf, "__" ++ name ++ "_end"),
+ .value = shndx.vaddr(elf),
+ .size = 0,
+ .type = .NOTYPE,
+ .bind = .strong,
+ .visibility = .HIDDEN,
+ .shndx = shndx.*,
+ }) catch |err| switch (err) {
+ error.MultipleDefinitions => return elf.base.comp.link_diags.fail(
+ "multiple definitions of '{s}'",
+ .{"__" ++ name ++ "_end"},
+ ),
+ };
+}
+
pub fn prelink(elf: *Elf, prog_node: std.Progress.Node) !void {
_ = prog_node;
elf.prelinkInner() catch |err| switch (err) {
@@ -3489,6 +3600,9 @@ fn prelinkInner(elf: *Elf) !void {
const needed_len = elf.needed.count();
const dynamic_len = needed_len + @intFromBool(elf.options.soname != null) +
@intFromBool(flags != 0) + @intFromBool(flags_1 != 0) +
+ @as(usize, @intFromBool(elf.shndx.init_array != .UNDEF)) * 2 +
+ @as(usize, @intFromBool(elf.shndx.fini_array != .UNDEF)) * 2 +
+ @as(usize, @intFromBool(elf.shndx.preinit_array != .UNDEF)) * 2 +
@intFromBool(comp.config.output_mode == .Exe) + 12;
const dynamic_size: u32 = @intCast(@sizeOf(ElfN.Addr) * 2 * dynamic_len);
const dynamic_ni = elf.shndx.dynamic.get(elf).ni;
@@ -3520,23 +3634,74 @@ fn prelinkInner(elf: *Elf) !void {
dynamic_entries[dynamic_index] = .{ std.elf.DT_DEBUG, 0 };
dynamic_index += 1;
}
+ if (elf.shndx.init_array != .UNDEF) {
+ dynamic_entries[dynamic_index..][0..2].* = .{
+ .{ std.elf.DT_INIT_ARRAY, @intCast(elf.shndx.init_array.vaddr(elf)) },
+ .{ std.elf.DT_INIT_ARRAYSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.init_array), @tagName(ct_class)).size,
+ ) },
+ };
+ try elf.ensureUnusedRelocCapacity(dynamic_ni, 1);
+ elf.addRelocAssumeCapacity(
+ dynamic_ni,
+ @sizeOf(ElfN.Addr) * (2 * dynamic_index + 1),
+ .local(elf.shndx.init_array.get(elf).lsi),
+ 0,
+ .absAddr(elf),
+ );
+ dynamic_index += 2;
+ }
+ if (elf.shndx.fini_array != .UNDEF) {
+ dynamic_entries[dynamic_index..][0..2].* = .{
+ .{ std.elf.DT_FINI_ARRAY, @intCast(elf.shndx.fini_array.vaddr(elf)) },
+ .{ std.elf.DT_FINI_ARRAYSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.fini_array), @tagName(ct_class)).size,
+ ) },
+ };
+ try elf.ensureUnusedRelocCapacity(dynamic_ni, 1);
+ elf.addRelocAssumeCapacity(
+ dynamic_ni,
+ @sizeOf(ElfN.Addr) * (2 * dynamic_index + 1),
+ .local(elf.shndx.fini_array.get(elf).lsi),
+ 0,
+ .absAddr(elf),
+ );
+ dynamic_index += 2;
+ }
+ if (elf.shndx.preinit_array != .UNDEF) {
+ dynamic_entries[dynamic_index..][0..2].* = .{
+ .{ std.elf.DT_PREINIT_ARRAY, @intCast(elf.shndx.preinit_array.vaddr(elf)) },
+ .{ std.elf.DT_PREINIT_ARRAYSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.preinit_array), @tagName(ct_class)).size,
+ ) },
+ };
+ try elf.ensureUnusedRelocCapacity(dynamic_ni, 1);
+ elf.addRelocAssumeCapacity(
+ dynamic_ni,
+ @sizeOf(ElfN.Addr) * (2 * dynamic_index + 1),
+ .local(elf.shndx.preinit_array.get(elf).lsi),
+ 0,
+ .absAddr(elf),
+ );
+ dynamic_index += 2;
+ }
const rela_dyn_shndx = elf.shndx.got.get(elf).rela_shndx;
const rela_plt_shndx = elf.shndx.got_plt.get(elf).rela_shndx;
dynamic_entries[dynamic_index..][0..12].* = .{
- .{ std.elf.DT_RELA, @intCast(elf.computeNodeVAddr(rela_dyn_shndx.get(elf).ni)) },
+ .{ std.elf.DT_RELA, @intCast(rela_dyn_shndx.vaddr(elf)) },
.{ std.elf.DT_RELASZ, elf.targetLoad(
&@field(elf.shdrPtr(rela_dyn_shndx), @tagName(ct_class)).size,
) },
.{ std.elf.DT_RELAENT, @sizeOf(ElfN.Rela) },
- .{ std.elf.DT_JMPREL, @intCast(elf.computeNodeVAddr(rela_plt_shndx.get(elf).ni)) },
+ .{ std.elf.DT_JMPREL, @intCast(rela_plt_shndx.vaddr(elf)) },
.{ std.elf.DT_PLTRELSZ, elf.targetLoad(
&@field(elf.shdrPtr(rela_plt_shndx), @tagName(ct_class)).size,
) },
- .{ std.elf.DT_PLTGOT, @intCast(elf.computeNodeVAddr(elf.shndx.got_plt.get(elf).ni)) },
+ .{ std.elf.DT_PLTGOT, @intCast(elf.shndx.got_plt.vaddr(elf)) },
.{ std.elf.DT_PLTREL, std.elf.DT_RELA },
- .{ std.elf.DT_SYMTAB, @intCast(elf.computeNodeVAddr(elf.shndx.dynsym.get(elf).ni)) },
+ .{ std.elf.DT_SYMTAB, @intCast(elf.shndx.dynsym.vaddr(elf)) },
.{ std.elf.DT_SYMENT, @sizeOf(ElfN.Sym) },
- .{ std.elf.DT_STRTAB, @intCast(elf.computeNodeVAddr(elf.shndx.dynstr.get(elf).ni)) },
+ .{ std.elf.DT_STRTAB, @intCast(elf.shndx.dynstr.vaddr(elf)) },
.{ std.elf.DT_STRSZ, elf.targetLoad(
&@field(elf.shdrPtr(elf.shndx.dynstr), @tagName(ct_class)).size,
) },
@@ -4414,6 +4579,22 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
}
}
}
+
+ // Update global symbols targeting this section
+ if (elf.node_global_symbols.get(ni)) |first_name| {
+ assert(first_name != .empty);
+ const old_addr = elf.targetLoad(&shdr.addr);
+ var name = first_name;
+ while (name != .empty) {
+ const global = elf.globalByName(name).?;
+ const old_sym_addr: u64 = switch (elf.symPtr(global.symtab_index)) {
+ inline else => |sym| elf.targetLoad(&sym.value),
+ };
+ global.flushMoved(elf, old_sym_addr - old_addr + addr);
+ name = global.next_in_node;
+ }
+ }
+
elf.targetStore(&shdr.addr, @intCast(addr));
shndx.get(elf).lsi.index().flushMoved(elf, addr);
}
@@ -4547,6 +4728,63 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void {
.NULL => if (size > 0) elf.targetStore(&shdr.type, .PROGBITS),
.PROGBITS => if (size == 0) elf.targetStore(&shdr.type, .NULL),
.SYMTAB, .DYNAMIC, .REL, .DYNSYM => return,
+ .INIT_ARRAY => {
+ assert(shndx == elf.shndx.init_array);
+ if (elf.shndx.dynamic != .UNDEF) {
+ const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast(
+ elf.shndx.dynamic.get(elf).ni.slice(&elf.mf),
+ ));
+ for (dynamic_entries) |*dynamic_entry|
+ switch (elf.targetLoad(&dynamic_entry[0])) {
+ else => {},
+ std.elf.DT_INIT_ARRAYSZ => dynamic_entry[1] = shdr.size,
+ };
+ }
+ const end_sym_index = elf.globalByName(elf.string(.strtab, "__init_array_end") catch unreachable).?.symtab_index;
+ const end_sym_ptr = @field(elf.symPtr(end_sym_index), @tagName(class));
+ const end_vaddr = shndx.vaddr(elf) + elf.targetLoad(&shdr.size);
+ elf.targetStore(&end_sym_ptr.value, @intCast(end_vaddr));
+ end_sym_index.flushMoved(elf, end_vaddr);
+ return;
+ },
+ .FINI_ARRAY => {
+ assert(shndx == elf.shndx.fini_array);
+ if (elf.shndx.dynamic != .UNDEF) {
+ const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast(
+ elf.shndx.dynamic.get(elf).ni.slice(&elf.mf),
+ ));
+ for (dynamic_entries) |*dynamic_entry|
+ switch (elf.targetLoad(&dynamic_entry[0])) {
+ else => {},
+ std.elf.DT_FINI_ARRAYSZ => dynamic_entry[1] = shdr.size,
+ };
+ }
+ const end_sym_index = elf.globalByName(elf.string(.strtab, "__fini_array_end") catch unreachable).?.symtab_index;
+ const end_sym_ptr = @field(elf.symPtr(end_sym_index), @tagName(class));
+ const end_vaddr = shndx.vaddr(elf) + elf.targetLoad(&shdr.size);
+ elf.targetStore(&end_sym_ptr.value, @intCast(end_vaddr));
+ end_sym_index.flushMoved(elf, end_vaddr);
+ return;
+ },
+ .PREINIT_ARRAY => {
+ assert(shndx == elf.shndx.preinit_array);
+ if (elf.shndx.dynamic != .UNDEF) {
+ const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast(
+ elf.shndx.dynamic.get(elf).ni.slice(&elf.mf),
+ ));
+ for (dynamic_entries) |*dynamic_entry|
+ switch (elf.targetLoad(&dynamic_entry[0])) {
+ else => {},
+ std.elf.DT_PREINIT_ARRAYSZ => dynamic_entry[1] = shdr.size,
+ };
+ }
+ const end_sym_index = elf.globalByName(elf.string(.strtab, "__preinit_array_end") catch unreachable).?.symtab_index;
+ const end_sym_ptr = @field(elf.symPtr(end_sym_index), @tagName(class));
+ const end_vaddr = shndx.vaddr(elf) + elf.targetLoad(&shdr.size);
+ elf.targetStore(&end_sym_ptr.value, @intCast(end_vaddr));
+ end_sym_index.flushMoved(elf, end_vaddr);
+ return;
+ },
.STRTAB => {
if (elf.shndx.dynamic != .UNDEF) {
if (shndx == elf.shndx.dynstr) {