From 6f0476e41d2f9b040a9883b288171c6a50c29ed5 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 29 Oct 2025 18:05:49 -0400 Subject: [PATCH 1/5] Elf2: start implementing input object loading --- src/Compilation.zig | 32 +-- src/link.zig | 48 ++-- src/link/Coff.zig | 56 +++-- src/link/Elf2.zig | 526 ++++++++++++++++++++++++++++++++++------ src/link/MappedFile.zig | 30 ++- 5 files changed, 537 insertions(+), 155 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 3670bc51b5..99d4d80b92 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -258,8 +258,6 @@ test_filters: []const []const u8, link_task_wait_group: WaitGroup = .{}, link_prog_node: std.Progress.Node = .none, -link_const_prog_node: std.Progress.Node = .none, -link_synth_prog_node: std.Progress.Node = .none, llvm_opt_bisect_limit: c_int, @@ -3066,35 +3064,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE // we also want it around during `flush`. if (comp.bin_file) |lf| { comp.link_prog_node = main_progress_node.start("Linking", 0); - if (lf.cast(.elf2)) |elf| { - comp.link_prog_node.increaseEstimatedTotalItems(3); - comp.link_const_prog_node = comp.link_prog_node.start("Constants", 0); - comp.link_synth_prog_node = comp.link_prog_node.start("Synthetics", 0); - elf.mf.update_prog_node = comp.link_prog_node.start("Relocations", elf.mf.updates.items.len); - } else if (lf.cast(.coff2)) |coff| { - comp.link_prog_node.increaseEstimatedTotalItems(3); - comp.link_const_prog_node = comp.link_prog_node.start("Constants", 0); - comp.link_synth_prog_node = comp.link_prog_node.start("Synthetics", 0); - coff.mf.update_prog_node = comp.link_prog_node.start("Relocations", coff.mf.updates.items.len); - } + lf.startProgress(comp.link_prog_node); } - defer { + defer if (comp.bin_file) |lf| { + lf.endProgress(); comp.link_prog_node.end(); comp.link_prog_node = .none; - comp.link_const_prog_node.end(); - comp.link_const_prog_node = .none; - comp.link_synth_prog_node.end(); - comp.link_synth_prog_node = .none; - if (comp.bin_file) |lf| { - if (lf.cast(.elf2)) |elf| { - elf.mf.update_prog_node.end(); - elf.mf.update_prog_node = .none; - } else if (lf.cast(.coff2)) |coff| { - coff.mf.update_prog_node.end(); - coff.mf.update_prog_node = .none; - } - } - } + }; try comp.performAllTheWork(main_progress_node); diff --git a/src/link.zig b/src/link.zig index 7cf8e5c1a6..eb6e3bb90b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -571,6 +571,26 @@ pub const File = struct { return if (dev.env.supports(tag.devFeature()) and base.tag == tag) @fieldParentPtr("base", base) else null; } + pub fn startProgress(base: *File, prog_node: std.Progress.Node) void { + switch (base.tag) { + else => {}, + inline .elf2, .coff2 => |tag| { + dev.check(tag.devFeature()); + return @as(*tag.Type(), @fieldParentPtr("base", base)).startProgress(prog_node); + }, + } + } + + pub fn endProgress(base: *File) void { + switch (base.tag) { + else => {}, + inline .elf2, .coff2 => |tag| { + dev.check(tag.devFeature()); + return @as(*tag.Type(), @fieldParentPtr("base", base)).endProgress(); + }, + } + } + pub fn makeWritable(base: *File) !void { dev.check(.make_writable); const comp = base.comp; @@ -620,10 +640,10 @@ pub const File = struct { &coff.mf else unreachable; - mf.file = .adaptFromNewApi(try Io.Dir.openFile(base.emit.root_dir.handle.adaptToNewApi(), io, base.emit.sub_path, .{ + mf.file = try base.emit.root_dir.handle.adaptToNewApi().openFile(io, base.emit.sub_path, .{ .mode = .read_write, - })); - base.file = mf.file; + }); + base.file = .adaptFromNewApi(mf.file); try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), @@ -648,6 +668,7 @@ pub const File = struct { pub fn makeExecutable(base: *File) !void { dev.check(.make_executable); const comp = base.comp; + const io = comp.io; switch (comp.config.output_mode) { .Obj => return, .Lib => switch (comp.config.link_mode) { @@ -698,8 +719,8 @@ pub const File = struct { unreachable; mf.unmap(); assert(mf.file.handle == f.handle); + mf.file.close(io); mf.file = undefined; - f.close(); base.file = null; }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), @@ -1120,7 +1141,7 @@ pub const File = struct { pub fn loadInput(base: *File, input: Input) anyerror!void { if (base.tag == .lld) return; switch (base.tag) { - inline .elf, .wasm => |tag| { + inline .elf, .elf2, .wasm => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input); }, @@ -1281,9 +1302,6 @@ pub const PrelinkTask = union(enum) { /// Tells the linker to load a shared library, possibly one that is a /// GNU ld script. load_dso: Path, - /// Tells the linker to load an input which could be an object file, - /// archive, or shared library. - load_input: Input, }; pub const ZcuTask = union(enum) { /// Write the constant value for a Decl to the output file. @@ -1461,20 +1479,6 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}), }; }, - .load_input => |input| { - const prog_node = comp.link_prog_node.start("Parse Input", 0); - defer prog_node.end(); - base.loadInput(input) catch |err| switch (err) { - error.LinkFailure => return, // error reported via link_diags - else => |e| { - if (input.path()) |path| { - diags.addParseError(path, "failed to parse linker input: {s}", .{@errorName(e)}); - } else { - diags.addError("failed to {s}: {s}", .{ input.taskName(), @errorName(e) }); - } - }, - }; - }, } } pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 32889684dc..31bbe07c88 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -26,6 +26,8 @@ pending_uavs: std.AutoArrayHashMapUnmanaged(Node.UavMapIndex, struct { src_loc: Zcu.LazySrcLoc, }), relocs: std.ArrayList(Reloc), +const_prog_node: std.Progress.Node, +synth_prog_node: std.Progress.Node, pub const default_file_alignment: u16 = 0x200; pub const default_size_of_stack_reserve: u32 = 0x1000000; @@ -630,11 +632,11 @@ fn create( }; const coff = try arena.create(Coff); - const file = try path.root_dir.handle.createFile(path.sub_path, .{ + const file = try path.root_dir.handle.adaptToNewApi().createFile(comp.io, path.sub_path, .{ .read = true, .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); - errdefer file.close(); + errdefer file.close(comp.io); coff.* = .{ .base = .{ .tag = .coff2, @@ -642,7 +644,7 @@ fn create( .comp = comp, .emit = path, - .file = file, + .file = .adaptFromNewApi(file), .gc_sections = false, .print_gc_sections = false, .build_id = .none, @@ -671,6 +673,8 @@ fn create( }), .pending_uavs = .empty, .relocs = .empty, + .const_prog_node = .none, + .synth_prog_node = .none, }; errdefer coff.deinit(); @@ -973,6 +977,26 @@ fn initHeaders( assert(coff.nodes.len == expected_nodes_len); } +pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void { + prog_node.increaseEstimatedTotalItems(3); + coff.const_prog_node = prog_node.start("Constants", coff.pending_uavs.count()); + coff.synth_prog_node = prog_node.start("Synthetics", count: { + var count = coff.globals.count() - coff.global_pending_index; + for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index; + break :count count; + }); + coff.mf.update_prog_node = prog_node.start("Relocations", coff.mf.updates.items.len); +} + +pub fn endProgress(coff: *Coff) void { + coff.mf.update_prog_node.end(); + coff.mf.update_prog_node = .none; + coff.synth_prog_node.end(); + coff.synth_prog_node = .none; + coff.const_prog_node.end(); + coff.const_prog_node = .none; +} + fn getNode(coff: *const Coff, ni: MappedFile.Node.Index) Node { return coff.nodes.get(@intFromEnum(ni)); } @@ -1172,7 +1196,7 @@ pub fn globalSymbol(coff: *Coff, name: []const u8, lib_name: ?[]const u8) !Symbo }); if (!sym_gop.found_existing) { sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); - coff.base.comp.link_synth_prog_node.increaseEstimatedTotalItems(1); + coff.synth_prog_node.increaseEstimatedTotalItems(1); } return sym_gop.value_ptr.*; } @@ -1250,7 +1274,7 @@ pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index { const sym_gop = try coff.lazy.getPtr(lazy.kind).map.getOrPut(gpa, lazy.ty); if (!sym_gop.found_existing) { sym_gop.value_ptr.* = try coff.initSymbolAssumeCapacity(); - coff.base.comp.link_synth_prog_node.increaseEstimatedTotalItems(1); + coff.synth_prog_node.increaseEstimatedTotalItems(1); } return sym_gop.value_ptr.*; } @@ -1585,7 +1609,7 @@ pub fn lowerUav( .alignment = uav_align, .src_loc = src_loc, }; - coff.base.comp.link_const_prog_node.increaseEstimatedTotalItems(1); + coff.const_prog_node.increaseEstimatedTotalItems(1); } } return .{ .sym_index = @intFromEnum(si) }; @@ -1726,17 +1750,16 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { const comp = coff.base.comp; task: { while (coff.pending_uavs.pop()) |pending_uav| { - const sub_prog_node = - coff.idleProgNode(tid, comp.link_const_prog_node, .{ .uav = pending_uav.key }); + const sub_prog_node = coff.idleProgNode(tid, coff.const_prog_node, .{ .uav = pending_uav.key }); defer sub_prog_node.end(); coff.flushUav( - .{ .zcu = coff.base.comp.zcu.?, .tid = tid }, + .{ .zcu = comp.zcu.?, .tid = tid }, pending_uav.key, pending_uav.value.alignment, pending_uav.value.src_loc, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.comp.link_diags.fail( + else => |e| return comp.link_diags.fail( "linker failed to lower constant: {t}", .{e}, ), @@ -1744,17 +1767,17 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } if (coff.global_pending_index < coff.globals.count()) { - const pt: Zcu.PerThread = .{ .zcu = coff.base.comp.zcu.?, .tid = tid }; + const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid }; const gmi: Node.GlobalMapIndex = @enumFromInt(coff.global_pending_index); coff.global_pending_index += 1; - const sub_prog_node = comp.link_synth_prog_node.start( + const sub_prog_node = coff.synth_prog_node.start( gmi.globalName(coff).name.toSlice(coff), 0, ); defer sub_prog_node.end(); coff.flushGlobal(pt, gmi) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.comp.link_diags.fail( + else => |e| return comp.link_diags.fail( "linker failed to lower constant: {t}", .{e}, ), @@ -1763,7 +1786,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } var lazy_it = coff.lazy.iterator(); while (lazy_it.next()) |lazy| if (lazy.value.pending_index < lazy.value.map.count()) { - const pt: Zcu.PerThread = .{ .zcu = coff.base.comp.zcu.?, .tid = tid }; + const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid }; const lmr: Node.LazyMapRef = .{ .kind = lazy.key, .index = lazy.value.pending_index }; lazy.value.pending_index += 1; const kind = switch (lmr.kind) { @@ -1771,7 +1794,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { .const_data => "data", }; var name: [std.Progress.Node.max_name_len]u8 = undefined; - const sub_prog_node = comp.link_synth_prog_node.start( + const sub_prog_node = coff.synth_prog_node.start( std.fmt.bufPrint(&name, "lazy {s} for {f}", .{ kind, Type.fromInterned(lmr.lazySymbol(coff).ty).fmt(pt), @@ -1781,7 +1804,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { defer sub_prog_node.end(); coff.flushLazy(pt, lmr) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => |e| return coff.base.comp.link_diags.fail( + else => |e| return comp.link_diags.fail( "linker failed to lower lazy {s}: {t}", .{ kind, e }, ), @@ -1802,6 +1825,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { } } if (coff.pending_uavs.count() > 0) return true; + if (coff.globals.count() > coff.global_pending_index) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; if (coff.mf.updates.items.len > 0) return true; return false; diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 93fa490907..b7a27f1dcd 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -6,6 +6,18 @@ phdrs: std.ArrayList(MappedFile.Node.Index), symtab: std.ArrayList(Symbol), shstrtab: StringTable, strtab: StringTable, +inputs: std.ArrayList(struct { + path: std.Build.Cache.Path, + si: Symbol.Index, +}), +input_sections: std.ArrayList(struct { + ii: Node.InputIndex, + ni: MappedFile.Node.Index, + addr: u64, + offset: u64, + size: u64, +}), +input_section_pending_index: u32, globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index), navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Symbol.Index), uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Symbol.Index), @@ -20,6 +32,9 @@ pending_uavs: std.AutoArrayHashMapUnmanaged(Node.UavMapIndex, struct { relocs: std.ArrayList(Reloc), /// This is hiding actual bugs with global symbols! Reconsider once they are implemented correctly. entry_hack: Symbol.Index, +const_prog_node: std.Progress.Node, +synth_prog_node: std.Progress.Node, +input_prog_node: std.Progress.Node, pub const Node = union(enum) { file, @@ -27,11 +42,53 @@ pub const Node = union(enum) { shdr, segment: u32, section: Symbol.Index, + input_section: InputSectionIndex, nav: NavMapIndex, uav: UavMapIndex, lazy_code: LazyMapRef.Index(.code), lazy_const_data: LazyMapRef.Index(.const_data), + pub const InputIndex = enum(u32) { + _, + + pub fn path(ii: InputIndex, elf: *const Elf) std.Build.Cache.Path { + return elf.inputs.items[@intFromEnum(ii)].path; + } + + pub fn symbol(ii: InputIndex, elf: *const Elf) Symbol.Index { + return elf.inputs.items[@intFromEnum(ii)].si; + } + + pub fn nextSymbol(ii: InputIndex, elf: *const Elf) Symbol.Index { + const next_ii = @intFromEnum(ii) + 1; + return if (next_ii < elf.inputs.items.len) + @as(InputIndex, @enumFromInt(next_ii)).symbol(elf) + else + @enumFromInt(elf.symtab.items.len); + } + }; + + pub const InputSectionIndex = enum(u32) { + _, + + pub fn input(isi: InputSectionIndex, elf: *const Elf) InputIndex { + return elf.input_sections.items[@intFromEnum(isi)].ii; + } + + pub fn node(isi: InputSectionIndex, elf: *const Elf) MappedFile.Node.Index { + return elf.input_sections.items[@intFromEnum(isi)].ni; + } + + pub fn addr(isi: InputSectionIndex, elf: *Elf) *u64 { + return &elf.input_sections.items[@intFromEnum(isi)].addr; + } + + pub fn fileLocation(isi: InputSectionIndex, elf: *const Elf) struct { u64, u64 } { + const input_section = &elf.input_sections.items[@intFromEnum(isi)]; + return .{ input_section.offset, input_section.size }; + } + }; + pub const NavMapIndex = enum(u32) { _, @@ -189,9 +246,14 @@ pub const Symbol = struct { return ni; } + pub fn next(si: Symbol.Index) Symbol.Index { + return @enumFromInt(@intFromEnum(si) + 1); + } + pub const InitOptions = struct { name: []const u8 = "", - size: std.elf.Word = 0, + value: u64 = 0, + size: u64 = 0, type: std.elf.STT, bind: std.elf.STB = .LOCAL, visibility: std.elf.STV = .DEFAULT, @@ -211,8 +273,8 @@ pub const Symbol = struct { switch (elf.symPtr(si)) { inline else => |sym| sym.* = .{ .name = name_entry, - .value = 0, - .size = opts.size, + .value = @intCast(opts.value), + .size = @intCast(opts.size), .info = .{ .type = opts.type, .bind = opts.bind, @@ -225,8 +287,7 @@ pub const Symbol = struct { } } - pub fn flushMoved(si: Symbol.Index, elf: *Elf) void { - const value = elf.computeNodeVAddr(si.node(elf)); + pub fn flushMoved(si: Symbol.Index, elf: *Elf, value: u64) void { switch (elf.symPtr(si)) { inline else => |sym, class| { elf.targetStore(&sym.value, @intCast(value)); @@ -443,11 +504,11 @@ fn create( }; const elf = try arena.create(Elf); - const file = try path.root_dir.handle.createFile(path.sub_path, .{ + const file = try path.root_dir.handle.adaptToNewApi().createFile(comp.io, path.sub_path, .{ .read = true, .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); - errdefer file.close(); + errdefer file.close(comp.io); elf.* = .{ .base = .{ .tag = .elf2, @@ -455,7 +516,7 @@ fn create( .comp = comp, .emit = path, - .file = file, + .file = .adaptFromNewApi(file), .gc_sections = false, .print_gc_sections = false, .build_id = .none, @@ -477,6 +538,9 @@ fn create( .map = .empty, .size = 1, }, + .inputs = .empty, + .input_sections = .empty, + .input_section_pending_index = 0, .globals = .empty, .navs = .empty, .uavs = .empty, @@ -487,6 +551,9 @@ fn create( .pending_uavs = .empty, .relocs = .empty, .entry_hack = .null, + .const_prog_node = .none, + .synth_prog_node = .none, + .input_prog_node = .none, }; errdefer elf.deinit(); @@ -502,6 +569,8 @@ pub fn deinit(elf: *Elf) void { elf.symtab.deinit(gpa); elf.shstrtab.map.deinit(gpa); elf.strtab.map.deinit(gpa); + elf.inputs.deinit(gpa); + elf.input_sections.deinit(gpa); elf.globals.deinit(gpa); elf.navs.deinit(gpa); elf.uavs.deinit(gpa); @@ -796,16 +865,18 @@ fn initHeaders( .addralign = elf.mf.flags.block_size, .entsize = 1, }) == .shstrtab); - assert(try elf.addSection(Node.Known.rodata, .{ - .type = std.elf.SHT_STRTAB, - .addralign = elf.mf.flags.block_size, - .entsize = 1, - }) == .strtab); try elf.renameSection(.symtab, ".symtab"); try elf.renameSection(.shstrtab, ".shstrtab"); - try elf.renameSection(.strtab, ".strtab"); - try elf.linkSections(.symtab, .strtab); Symbol.Index.shstrtab.node(elf).slice(&elf.mf)[0] = 0; + + assert(try elf.addSection(Node.Known.rodata, .{ + .name = ".strtab", + .type = std.elf.SHT_STRTAB, + .addralign = elf.mf.flags.block_size, + .size = 1, + .entsize = 1, + }) == .strtab); + try elf.linkSections(.symtab, .strtab); Symbol.Index.strtab.node(elf).slice(&elf.mf)[0] = 0; assert(try elf.addSection(Node.Known.rodata, .{ @@ -860,6 +931,32 @@ fn initHeaders( assert(elf.nodes.len == expected_nodes_len); } +pub fn startProgress(elf: *Elf, prog_node: std.Progress.Node) void { + prog_node.increaseEstimatedTotalItems(4); + elf.const_prog_node = prog_node.start("Constants", elf.pending_uavs.count()); + elf.synth_prog_node = prog_node.start("Synthetics", count: { + var count: usize = 0; + for (&elf.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index; + break :count count; + }); + elf.mf.update_prog_node = prog_node.start("Relocations", elf.mf.updates.items.len); + elf.input_prog_node = prog_node.start( + "Inputs", + elf.input_sections.items.len - elf.input_section_pending_index, + ); +} + +pub fn endProgress(elf: *Elf) void { + elf.input_prog_node.end(); + elf.input_prog_node = .none; + elf.mf.update_prog_node.end(); + elf.mf.update_prog_node = .none; + elf.synth_prog_node.end(); + elf.synth_prog_node = .none; + elf.const_prog_node.end(); + elf.const_prog_node = .none; +} + fn getNode(elf: *const Elf, ni: MappedFile.Node.Index) Node { return elf.nodes.get(@intFromEnum(ni)); } @@ -871,6 +968,7 @@ fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { inline else => |ph| elf.targetLoad(&ph[phndx].vaddr), }, .section => |si| si, + .input_section => unreachable, inline .nav, .uav, .lazy_code, .lazy_const_data => |mi| mi.symbol(elf), }; break :parent_vaddr switch (elf.symPtr(parent_si)) { @@ -1072,21 +1170,24 @@ fn navType( }, }; } +fn namedSection(name: []const u8) ?Symbol.Index { + if (std.mem.eql(u8, name, ".rodata") or + std.mem.startsWith(u8, name, ".rodata.")) return .rodata; + if (std.mem.eql(u8, name, ".text") or + std.mem.startsWith(u8, name, ".text.")) return .text; + if (std.mem.eql(u8, name, ".data") or + std.mem.startsWith(u8, name, ".data.")) return .data; + if (std.mem.eql(u8, name, ".tdata") or + std.mem.startsWith(u8, name, ".tdata.")) return .tdata; + return null; +} fn navSection( elf: *Elf, ip: *const InternPool, nav_fr: @FieldType(@FieldType(InternPool.Nav, "status"), "fully_resolved"), ) Symbol.Index { - if (nav_fr.@"linksection".toSlice(ip)) |@"linksection"| { - if (std.mem.eql(u8, @"linksection", ".rodata") or - std.mem.startsWith(u8, @"linksection", ".rodata.")) return .rodata; - if (std.mem.eql(u8, @"linksection", ".text") or - std.mem.startsWith(u8, @"linksection", ".text.")) return .text; - if (std.mem.eql(u8, @"linksection", ".data") or - std.mem.startsWith(u8, @"linksection", ".data.")) return .data; - if (std.mem.eql(u8, @"linksection", ".tdata") or - std.mem.startsWith(u8, @"linksection", ".tdata.")) return .tdata; - } + if (nav_fr.@"linksection".toSlice(ip)) |@"linksection"| + if (namedSection(@"linksection")) |si| return si; return switch (navType( ip, .{ .fully_resolved = nav_fr }, @@ -1156,11 +1257,236 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index { .const_data => .OBJECT, }, }); - elf.base.comp.link_synth_prog_node.increaseEstimatedTotalItems(1); + elf.synth_prog_node.increaseEstimatedTotalItems(1); } return lazy_gop.value_ptr.*; } +pub fn loadInput(elf: *Elf, input: link.Input) !void { + const comp = elf.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + switch (input) { + .object => |object| { + const ii: Node.InputIndex = @enumFromInt(elf.inputs.items.len); + log.debug("loadInput(.{{ .object = {f} }}) = {d}", .{ object.path, ii }); + { + try elf.symtab.ensureUnusedCapacity(gpa, 1); + try elf.inputs.ensureUnusedCapacity(gpa, 1); + const si = try elf.initSymbolAssumeCapacity(.{ + .name = std.fs.path.stem(object.path.sub_path), + .type = .FILE, + .shndx = std.elf.SHN_ABS, + }); + elf.inputs.addOneAssumeCapacity().* = .{ + .path = object.path, + .si = si, + }; + } + var buf: [4096]u8 = undefined; + var fr = object.file.reader(comp.io, &buf); + const r = &fr.interface; + const ident = try r.peek(std.elf.EI.NIDENT); + if (!std.mem.eql(u8, elf.mf.contents[0..std.elf.EI.NIDENT], ident)) + return diags.failParse(object.path, "wrong ident", .{}); + const target_endian = elf.targetEndian(); + switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| { + const ehdr = try r.peekStruct( + @typeInfo(@FieldType(EhdrPtr, @tagName(class))).pointer.child, + target_endian, + ); + if (ehdr.type != .REL) + return diags.failParse(object.path, "unsupported object type", .{}); + if (ehdr.machine != elf.ehdrField(.machine)) + return diags.failParse(object.path, "wrong machine", .{}); + if (ehdr.shoff == 0 or ehdr.shnum <= 1) return; + if (ehdr.shstrndx == std.elf.SHN_UNDEF or ehdr.shstrndx >= ehdr.shnum) + return diags.failParse(object.path, "missing section names", .{}); + const Shdr = @typeInfo(@FieldType(ShdrSlice, @tagName(class))).pointer.child; + if (ehdr.shentsize < @sizeOf(Shdr)) + return diags.failParse(object.path, "unsupported shentsize", .{}); + const shstrtab = shstrtab: { + try fr.seekTo(ehdr.shoff + ehdr.shentsize * ehdr.shstrndx); + const shdr = try r.peekStruct(Shdr, target_endian); + if (shdr.type != std.elf.SHT_STRTAB) + return diags.failParse(object.path, "invalid shstrtab type", .{}); + const shstrtab = try gpa.alloc(u8, @intCast(shdr.size)); + errdefer gpa.free(shstrtab); + try fr.seekTo(shdr.offset); + try r.readSliceAll(shstrtab); + break :shstrtab shstrtab; + }; + defer gpa.free(shstrtab); + const Sym = @typeInfo(@FieldType(SymPtr, @tagName(class))).pointer.child; + try fr.seekTo(ehdr.shoff + ehdr.shentsize); + var symtab_shdr: Shdr = .{ + .name = 0, + .type = 0, + .flags = .{ .shf = .{} }, + .addr = 0, + .offset = 0, + .size = 0, + .link = 0, + .info = 0, + .addralign = 0, + .entsize = 0, + }; + const sections = try gpa.alloc(struct { + shndx: u32, + ni: MappedFile.Node.Index, + }, ehdr.shnum); + defer gpa.free(sections); + @memset(sections, .{ .shndx = std.elf.SHN_UNDEF, .ni = .none }); + for (sections[1..ehdr.shnum]) |*section| { + const shdr = try r.peekStruct(Shdr, target_endian); + try r.discardAll(ehdr.shentsize); + switch (shdr.type) { + else => continue, + std.elf.SHT_PROGBITS => {}, + std.elf.SHT_SYMTAB => { + if (symtab_shdr.entsize > 0) + return diags.failParse(object.path, "too many symtabs", .{}); + if (shdr.entsize < @sizeOf(Sym)) + return diags.failParse(object.path, "unsupported entsize", .{}); + symtab_shdr = shdr; + continue; + }, + std.elf.SHT_NOBITS => {}, + } + if (shdr.name >= shstrtab.len) continue; + const name = std.mem.sliceTo(shstrtab[shdr.name..], 0); + const si = namedSection(name) orelse continue; + section.shndx = elf.targetLoad(&@field(elf.symPtr(si), @tagName(class)).shndx); + try elf.nodes.ensureUnusedCapacity(gpa, 1); + try elf.input_sections.ensureUnusedCapacity(gpa, 1); + section.ni = try elf.mf.addLastChildNode(gpa, si.node(elf), .{ + .size = shdr.size, + .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert( + usize, + @intCast(@max(shdr.addralign, 1)), + )), + .moved = true, + }); + elf.nodes.appendAssumeCapacity(.{ + .input_section = @enumFromInt(elf.input_sections.items.len), + }); + elf.input_sections.addOneAssumeCapacity().* = .{ + .ii = ii, + .ni = section.ni, + .addr = shdr.addr, + .offset = shdr.offset, + .size = shdr.size, + }; + elf.synth_prog_node.increaseEstimatedTotalItems(1); + } + if (symtab_shdr.info <= 1) return; + const strtab = strtab: { + try fr.seekTo(ehdr.shoff + ehdr.shentsize * symtab_shdr.link); + const shdr = try r.peekStruct(Shdr, target_endian); + if (shdr.type != std.elf.SHT_STRTAB) + return diags.failParse(object.path, "invalid strtab type", .{}); + const strtab = try gpa.alloc(u8, @intCast(shdr.size)); + errdefer gpa.free(strtab); + try fr.seekTo(shdr.offset); + try r.readSliceAll(strtab); + break :strtab strtab; + }; + defer gpa.free(strtab); + try elf.symtab.ensureUnusedCapacity(gpa, symtab_shdr.info); + try elf.globals.ensureUnusedCapacity(gpa, symtab_shdr.info); + try fr.seekTo(symtab_shdr.offset + symtab_shdr.entsize); + for (1..try std.math.divExact( + usize, + @intCast(symtab_shdr.size), + @intCast(symtab_shdr.entsize), + )) |_| { + const input_sym = try r.peekStruct(Sym, target_endian); + try r.discardAll64(symtab_shdr.entsize); + switch (input_sym.info.type) { + else => continue, + .OBJECT, .FUNC => {}, + } + if (input_sym.name >= strtab.len or input_sym.shndx >= sections.len) continue; + const name = std.mem.sliceTo(strtab[input_sym.name..], 0); + const si = try elf.initSymbolAssumeCapacity(.{ + .name = name, + .value = input_sym.value, + .size = input_sym.size, + .type = input_sym.info.type, + .bind = input_sym.info.bind, + .visibility = input_sym.other.visibility, + .shndx = @intCast(sections[input_sym.shndx].shndx), + }); + { + const sym = si.get(elf); + sym.ni = sections[input_sym.shndx].ni; + assert(sym.loc_relocs == .none); + sym.loc_relocs = @enumFromInt(elf.relocs.items.len); + } + switch (input_sym.info.bind) { + else => {}, + .GLOBAL => { + const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( + &@field(elf.symPtr(si), @tagName(class)).name, + )); + if (gop.found_existing) switch (elf.targetLoad( + switch (elf.symPtr(gop.value_ptr.*)) { + inline else => |sym| &sym.info, + }, + ).bind) { + else => unreachable, + .GLOBAL => return diags.failParse( + object.path, + "multiple definitions of '{s}'", + .{name}, + ), + .WEAK => {}, + }; + gop.value_ptr.* = si; + }, + .WEAK => { + const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( + &@field(elf.symPtr(si), @tagName(class)).name, + )); + if (!gop.found_existing) gop.value_ptr.* = si; + }, + } + } + }, + } + }, + else => {}, + } +} + +pub fn prelink(elf: *Elf, prog_node: std.Progress.Node) !void { + _ = prog_node; + elf.prelinkInner() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| return elf.base.comp.link_diags.fail("prelink failed: {t}", .{e}), + }; +} +fn prelinkInner(elf: *Elf) !void { + const gpa = elf.base.comp.gpa; + try elf.symtab.ensureUnusedCapacity(gpa, 1); + try elf.inputs.ensureUnusedCapacity(gpa, 1); + const zcu_name = try std.fmt.allocPrint(gpa, "{s}_zcu", .{ + std.fs.path.stem(elf.base.emit.sub_path), + }); + defer gpa.free(zcu_name); + const si = try elf.initSymbolAssumeCapacity(.{ + .name = zcu_name, + .type = .FILE, + .shndx = std.elf.SHN_ABS, + }); + elf.inputs.addOneAssumeCapacity().* = .{ + .path = elf.base.emit, + .si = si, + }; +} + pub fn getNavVAddr( elf: *Elf, pt: Zcu.PerThread, @@ -1228,12 +1554,7 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { const si = elf.addSymbolAssumeCapacity(); elf.nodes.appendAssumeCapacity(.{ .section = si }); si.get(elf).ni = ni; - try si.init(elf, .{ - .name = opts.name, - .size = opts.size, - .type = .SECTION, - .shndx = shndx, - }); + try si.init(elf, .{ .size = opts.size, .type = .SECTION, .shndx = shndx }); switch (elf.shdrSlice()) { inline else => |shdr| { const sh = &shdr[shndx]; @@ -1256,15 +1577,12 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { } fn renameSection(elf: *Elf, si: Symbol.Index, name: []const u8) !void { - const strtab_entry = try elf.string(.strtab, name); const shstrtab_entry = try elf.string(.shstrtab, name); switch (elf.shdrSlice()) { - inline else => |shdr, class| { - const sym = @field(elf.symPtr(si), @tagName(class)); - elf.targetStore(&sym.name, strtab_entry); - const sh = &shdr[elf.targetLoad(&sym.shndx)]; - elf.targetStore(&sh.name, shstrtab_entry); - }, + inline else => |shdr, class| elf.targetStore( + &shdr[elf.targetLoad(&@field(elf.symPtr(si), @tagName(class)).shndx)].name, + shstrtab_entry, + ), } } @@ -1323,11 +1641,6 @@ pub fn addReloc( target.target_relocs = ri; } -pub fn prelink(elf: *Elf, prog_node: std.Progress.Node) void { - _ = elf; - _ = prog_node; -} - pub fn updateNav(elf: *Elf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { elf.updateNavInner(pt, nav_index) catch |err| switch (err) { error.OutOfMemory, @@ -1430,7 +1743,7 @@ pub fn lowerUav( .alignment = uav_align, .src_loc = src_loc, }; - elf.base.comp.link_const_prog_node.increaseEstimatedTotalItems(1); + elf.const_prog_node.increaseEstimatedTotalItems(1); } } return .{ .sym_index = @intFromEnum(si) }; @@ -1534,7 +1847,7 @@ pub fn updateErrorData(elf: *Elf, pt: Zcu.PerThread) !void { }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.CodegenFail => return error.LinkFailure, - else => |e| return elf.base.comp.link_diags.fail("updateErrorData failed {t}", .{e}), + else => |e| return elf.base.comp.link_diags.fail("updateErrorData failed: {t}", .{e}), }; } @@ -1553,20 +1866,16 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { const comp = elf.base.comp; task: { while (elf.pending_uavs.pop()) |pending_uav| { - const sub_prog_node = elf.idleProgNode( - tid, - comp.link_const_prog_node, - .{ .uav = pending_uav.key }, - ); + const sub_prog_node = elf.idleProgNode(tid, elf.const_prog_node, .{ .uav = pending_uav.key }); defer sub_prog_node.end(); elf.flushUav( - .{ .zcu = elf.base.comp.zcu.?, .tid = tid }, + .{ .zcu = comp.zcu.?, .tid = tid }, pending_uav.key, pending_uav.value.alignment, pending_uav.value.src_loc, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => |e| return elf.base.comp.link_diags.fail( + else => |e| return comp.link_diags.fail( "linker failed to lower constant: {t}", .{e}, ), @@ -1575,7 +1884,7 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { } var lazy_it = elf.lazy.iterator(); while (lazy_it.next()) |lazy| if (lazy.value.pending_index < lazy.value.map.count()) { - const pt: Zcu.PerThread = .{ .zcu = elf.base.comp.zcu.?, .tid = tid }; + const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid }; const lmr: Node.LazyMapRef = .{ .kind = lazy.key, .index = lazy.value.pending_index }; lazy.value.pending_index += 1; const kind = switch (lmr.kind) { @@ -1583,7 +1892,7 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { .const_data => "data", }; var name: [std.Progress.Node.max_name_len]u8 = undefined; - const sub_prog_node = comp.link_synth_prog_node.start( + const sub_prog_node = elf.synth_prog_node.start( std.fmt.bufPrint(&name, "lazy {s} for {f}", .{ kind, Type.fromInterned(lmr.lazySymbol(elf).ty).fmt(pt), @@ -1593,13 +1902,27 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { defer sub_prog_node.end(); elf.flushLazy(pt, lmr) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => |e| return elf.base.comp.link_diags.fail( + else => |e| return comp.link_diags.fail( "linker failed to lower lazy {s}: {t}", .{ kind, e }, ), }; break :task; }; + if (elf.input_section_pending_index < elf.input_sections.items.len) { + const isi: Node.InputSectionIndex = @enumFromInt(elf.input_section_pending_index); + elf.input_section_pending_index += 1; + const sub_prog_node = elf.idleProgNode(tid, elf.input_prog_node, elf.getNode(isi.node(elf))); + defer sub_prog_node.end(); + elf.flushInputSection(isi) catch |err| switch (err) { + else => |e| return comp.link_diags.fail("linker failed to read input section '{s}' from \"{f}\": {t}", .{ + elf.sectionName(elf.getNode(isi.node(elf).parent(&elf.mf)).section), + isi.input(elf).path(elf).fmtEscapeString(), + e, + }), + }; + break :task; + } while (elf.mf.updates.pop()) |ni| { const clean_moved = ni.cleanMoved(&elf.mf); const clean_resized = ni.cleanResized(&elf.mf); @@ -1614,6 +1937,7 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { } if (elf.pending_uavs.count() > 0) return true; for (&elf.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; + if (elf.input_sections.items.len > elf.input_section_pending_index) return true; if (elf.mf.updates.items.len > 0) return true; return false; } @@ -1628,6 +1952,10 @@ fn idleProgNode( return prog_node.start(name: switch (node) { else => |tag| @tagName(tag), .section => |si| elf.sectionName(si), + .input_section => |isi| std.fmt.bufPrint(&name, "{f} {s}", .{ + isi.input(elf).path(elf), + elf.sectionName(elf.getNode(isi.node(elf).parent(&elf.mf)).section), + }) catch &name, .nav => |nmi| { const ip = &elf.base.comp.zcu.?.intern_pool; break :name ip.getNav(nmi.navIndex(elf)).fqn.toSlice(ip); @@ -1749,6 +2077,23 @@ fn flushLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { si.applyLocationRelocs(elf); } +fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { + const offset, const size = isi.fileLocation(elf); + if (size == 0) return; + const comp = elf.base.comp; + const gpa = comp.gpa; + const io = comp.io; + const path = isi.input(elf).path(elf); + const file = try path.root_dir.handle.adaptToNewApi().openFile(io, path.sub_path, .{}); + defer file.close(io); + var fr = file.reader(io, &.{}); + try fr.seekTo(offset); + var nw: MappedFile.Node.Writer = undefined; + isi.node(elf).writer(&elf.mf, gpa, &nw); + defer nw.deinit(); + if (try nw.interface.sendFileAll(&fr, .limited(@intCast(size))) != size) return error.EndOfStream; +} + fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { switch (elf.getNode(ni)) { .file => unreachable, @@ -1786,7 +2131,28 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { } }, }, - inline .nav, .uav, .lazy_code, .lazy_const_data => |mi| mi.symbol(elf).flushMoved(elf), + .input_section => |isi| { + const addr = isi.addr(elf); + const old_addr = addr.*; + const new_addr = elf.computeNodeVAddr(ni); + addr.* = new_addr; + const ii = isi.input(elf); + var si = ii.symbol(elf); + const end_si = ii.nextSymbol(elf); + while (cond: { + si = si.next(); + break :cond si != end_si; + }) { + if (si.get(elf).ni != ni) continue; + si.flushMoved(elf, switch (elf.symPtr(si)) { + inline else => |sym| elf.targetLoad(&sym.value), + } - old_addr + new_addr); + } + }, + inline .nav, .uav, .lazy_code, .lazy_const_data => |mi| mi.symbol(elf).flushMoved( + elf, + elf.computeNodeVAddr(ni), + ), } try ni.childrenMoved(elf.base.comp.gpa, &elf.mf); } @@ -1860,6 +2226,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void { } }, }, + .input_section => {}, .nav, .uav, .lazy_code, .lazy_const_data => {}, } } @@ -1983,6 +2350,10 @@ pub fn printNode( switch (node) { else => {}, .section => |si| try w.print("({s})", .{elf.sectionName(si)}), + .input_section => |isi| try w.print("({f}, {s})", .{ + isi.input(elf).path(elf), + elf.sectionName(elf.getNode(isi.node(elf).parent(&elf.mf)).section), + }), .nav => |nmi| { const zcu = elf.base.comp.zcu.?; const ip = &zcu.intern_pool; @@ -2027,25 +2398,28 @@ pub fn printNode( leaf = false; try elf.printNode(tid, w, child_ni, indent + 1); } - if (leaf) { - const file_loc = ni.fileLocation(&elf.mf, false); - if (file_loc.size == 0) return; - var address = file_loc.offset; - const line_len = 0x10; - var line_it = std.mem.window( - u8, - elf.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], - line_len, - line_len, - ); - while (line_it.next()) |line_bytes| : (address += line_len) { - try w.splatByteAll(' ', indent + 1); - try w.print("{x:0>8} ", .{address}); - for (line_bytes) |byte| try w.print("{x:0>2} ", .{byte}); - try w.splatByteAll(' ', 3 * (line_len - line_bytes.len) + 1); - for (line_bytes) |byte| try w.writeByte(if (std.ascii.isPrint(byte)) byte else '.'); - try w.writeByte('\n'); - } + if (!leaf) return; + const file_loc = ni.fileLocation(&elf.mf, false); + var address = file_loc.offset; + if (file_loc.size == 0) { + try w.splatByteAll(' ', indent + 1); + try w.print("{x:0>8}\n", .{address}); + return; + } + const line_len = 0x10; + var line_it = std.mem.window( + u8, + elf.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], + line_len, + line_len, + ); + while (line_it.next()) |line_bytes| : (address += line_len) { + try w.splatByteAll(' ', indent + 1); + try w.print("{x:0>8} ", .{address}); + for (line_bytes) |byte| try w.print("{x:0>2} ", .{byte}); + try w.splatByteAll(' ', 3 * (line_len - line_bytes.len) + 1); + for (line_bytes) |byte| try w.writeByte(if (std.ascii.isPrint(byte)) byte else '.'); + try w.writeByte('\n'); } } diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index a9d874c23f..7550638197 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1,4 +1,4 @@ -file: std.fs.File, +file: std.Io.File, flags: packed struct { block_size: std.mem.Alignment, copy_file_range_unsupported: bool, @@ -24,7 +24,7 @@ pub const Error = std.posix.MMapError || std.posix.MRemapError || std.fs.File.Se NoSpaceLeft, }; -pub fn init(file: std.fs.File, gpa: std.mem.Allocator) !MappedFile { +pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile { var mf: MappedFile = .{ .file = file, .flags = undefined, @@ -386,7 +386,7 @@ pub const Node = extern struct { fn sendFile( interface: *std.Io.Writer, - file_reader: *std.fs.File.Reader, + file_reader: *std.Io.File.Reader, limit: std.Io.Limit, ) std.Io.Writer.FileError!usize { if (limit == .nothing) return 0; @@ -397,14 +397,16 @@ pub const Node = extern struct { switch (file_reader.mode) { .positional => { const fr_buf = file_reader.interface.buffered(); - const buf_copy_size = interface.write(fr_buf) catch unreachable; - file_reader.interface.toss(buf_copy_size); - if (buf_copy_size < fr_buf.len) return buf_copy_size; - assert(file_reader.logicalPos() == file_reader.pos); + if (fr_buf.len > 0) { + const n = interface.write(fr_buf) catch unreachable; + file_reader.interface.toss(n); + return n; + } + assert(file_reader.logicalPos() == file_reader.pos); const w: *Writer = @fieldParentPtr("interface", interface); - const copy_size: usize = @intCast(w.mf.copyFileRange( - .adaptFromNewApi(file_reader.file), + const n: usize = @intCast(w.mf.copyFileRange( + file_reader.file, file_reader.pos, w.ni.fileLocation(w.mf, true).offset + interface.end, limit.minInt(interface.unusedCapacityLen()), @@ -412,8 +414,10 @@ pub const Node = extern struct { w.err = err; return error.WriteFailed; }); - interface.end += copy_size; - return copy_size; + if (n == 0) return error.Unimplemented; + file_reader.pos += n; + interface.end += n; + return n; }, .streaming, .streaming_reading, @@ -614,7 +618,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested // Resize the entire file if (ni == Node.Index.root) { try mf.ensureCapacityForSetLocation(gpa); - try mf.file.setEndPos(new_size); + try std.fs.File.adaptFromNewApi(mf.file).setEndPos(new_size); try mf.ensureTotalCapacity(@intCast(new_size)); ni.setLocationAssumeCapacity(mf, old_offset, new_size); return; @@ -894,7 +898,7 @@ fn copyRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: fn copyFileRange( mf: *MappedFile, - old_file: std.fs.File, + old_file: std.Io.File, old_file_offset: u64, new_file_offset: u64, size: u64, From 7542c3260f93e92fd8da7e5329bc80fd601964e9 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 29 Oct 2025 18:01:10 -0400 Subject: [PATCH 2/5] Elf2: load relocations from input objects --- lib/std/elf.zig | 46 ++++ src/Compilation.zig | 2 +- src/codegen/x86_64/Emit.zig | 12 +- src/link/Elf2.zig | 422 ++++++++++++++++++++---------------- src/link/MappedFile.zig | 6 +- 5 files changed, 289 insertions(+), 199 deletions(-) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index c5f8a7ea80..2f00cef22c 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -943,11 +943,30 @@ pub const Elf32 = struct { unused: u5 = 0, }; }; + pub const Rel = extern struct { + offset: Elf32.Addr, + info: Info, + addend: u0 = 0, + + pub const Info = packed struct(u32) { + type: u8, + sym: u24, + }; + }; + pub const Rela = extern struct { + offset: Elf32.Addr, + info: Info, + addend: i32, + + pub const Info = Elf32.Rel.Info; + }; comptime { assert(@sizeOf(Elf32.Ehdr) == 52); assert(@sizeOf(Elf32.Phdr) == 32); assert(@sizeOf(Elf32.Shdr) == 40); assert(@sizeOf(Elf32.Sym) == 16); + assert(@sizeOf(Elf32.Rel) == 8); + assert(@sizeOf(Elf32.Rela) == 12); } }; pub const Elf64 = struct { @@ -1008,11 +1027,30 @@ pub const Elf64 = struct { pub const Info = Elf32.Sym.Info; pub const Other = Elf32.Sym.Other; }; + pub const Rel = extern struct { + offset: Elf64.Addr, + info: Info, + addend: u0 = 0, + + pub const Info = packed struct(u64) { + type: u32, + sym: u32, + }; + }; + pub const Rela = extern struct { + offset: Elf64.Addr, + info: Info, + addend: i64, + + pub const Info = Elf64.Rel.Info; + }; comptime { assert(@sizeOf(Elf64.Ehdr) == 64); assert(@sizeOf(Elf64.Phdr) == 56); assert(@sizeOf(Elf64.Shdr) == 64); assert(@sizeOf(Elf64.Sym) == 24); + assert(@sizeOf(Elf64.Rel) == 16); + assert(@sizeOf(Elf64.Rela) == 24); } }; pub const ElfN = switch (@sizeOf(usize)) { @@ -1428,6 +1466,14 @@ pub const CLASS = enum(u8) { _, pub const NUM = @typeInfo(CLASS).@"enum".fields.len; + + pub fn ElfN(comptime class: CLASS) type { + return switch (class) { + .NONE, _ => comptime unreachable, + .@"32" => Elf32, + .@"64" => Elf64, + }; + } }; /// Deprecated, use `@intFromEnum(std.elf.DATA.NONE)` diff --git a/src/Compilation.zig b/src/Compilation.zig index 99d4d80b92..488f7db675 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1989,7 +1989,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, break :s if (is_exe_or_dyn_lib and build_options.have_llvm) .dyn_lib else .zcu; }, } - if (options.config.use_new_linker) break :s .zcu; + if (options.config.use_new_linker) break :s .obj; } if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm if (is_exe_or_dyn_lib) break :s .lib; diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index 3f4ef1b999..0aac146bc0 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -217,9 +217,7 @@ pub fn emitMir(emit: *Emit) Error!void { }, emit.lower.target), reloc_info), .mov => try emit.encodeInst(try .new(.none, .mov, &.{ lowered_inst.ops[0], - .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ - .base = .{ .reg = .ds }, - }) }, + .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{}) }, }, emit.lower.target), reloc_info), else => unreachable, } else if (reloc.target.is_extern) switch (lowered_inst.encoding.mnemonic) { @@ -720,7 +718,7 @@ pub fn emitMir(emit: *Emit) Error!void { for (emit.table_relocs.items) |table_reloc| try atom.addReloc(gpa, .{ .r_offset = table_reloc.source_offset, - .r_info = @as(u64, emit.atom_index) << 32 | @intFromEnum(std.elf.R_X86_64.@"32"), + .r_info = @as(u64, emit.atom_index) << 32 | @intFromEnum(std.elf.R_X86_64.@"32S"), .r_addend = @as(i64, table_offset) + table_reloc.target_offset, }, zo); for (emit.lower.mir.table) |entry| { @@ -738,7 +736,7 @@ pub fn emitMir(emit: *Emit) Error!void { table_reloc.source_offset, @enumFromInt(emit.atom_index), @as(i64, table_offset) + table_reloc.target_offset, - .{ .X86_64 = .@"32" }, + .{ .X86_64 = .@"32S" }, ); for (emit.lower.mir.table) |entry| { try elf.addReloc( @@ -824,7 +822,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI const zo = elf_file.zigObjectPtr().?; const atom = zo.symbol(emit.atom_index).atom(elf_file).?; const r_type: std.elf.R_X86_64 = if (!emit.pic) - .@"32" + .@"32S" else if (reloc.target.is_extern and !reloc.target.force_pcrel_direct) .GOTPCREL else @@ -855,7 +853,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI end_offset - 4, @enumFromInt(reloc.target.index), reloc.off, - .{ .X86_64 = .@"32" }, + .{ .X86_64 = .@"32S" }, ) else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( @enumFromInt(emit.atom_index), end_offset - 4, diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index b7a27f1dcd..162fe99796 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -12,10 +12,8 @@ inputs: std.ArrayList(struct { }), input_sections: std.ArrayList(struct { ii: Node.InputIndex, - ni: MappedFile.Node.Index, - addr: u64, - offset: u64, - size: u64, + si: Symbol.Index, + file_location: MappedFile.Node.FileLocation, }), input_section_pending_index: u32, globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index), @@ -59,7 +57,7 @@ pub const Node = union(enum) { return elf.inputs.items[@intFromEnum(ii)].si; } - pub fn nextSymbol(ii: InputIndex, elf: *const Elf) Symbol.Index { + pub fn endSymbol(ii: InputIndex, elf: *const Elf) Symbol.Index { const next_ii = @intFromEnum(ii) + 1; return if (next_ii < elf.inputs.items.len) @as(InputIndex, @enumFromInt(next_ii)).symbol(elf) @@ -75,17 +73,12 @@ pub const Node = union(enum) { return elf.input_sections.items[@intFromEnum(isi)].ii; } - pub fn node(isi: InputSectionIndex, elf: *const Elf) MappedFile.Node.Index { - return elf.input_sections.items[@intFromEnum(isi)].ni; + pub fn symbol(isi: InputSectionIndex, elf: *const Elf) Symbol.Index { + return elf.input_sections.items[@intFromEnum(isi)].si; } - pub fn addr(isi: InputSectionIndex, elf: *Elf) *u64 { - return &elf.input_sections.items[@intFromEnum(isi)].addr; - } - - pub fn fileLocation(isi: InputSectionIndex, elf: *const Elf) struct { u64, u64 } { - const input_section = &elf.input_sections.items[@intFromEnum(isi)]; - return .{ input_section.offset, input_section.size }; + pub fn fileLocation(isi: InputSectionIndex, elf: *const Elf) MappedFile.Node.FileLocation { + return elf.input_sections.items[@intFromEnum(isi)].file_location; } }; @@ -302,9 +295,12 @@ pub const Symbol = struct { } pub fn applyLocationRelocs(si: Symbol.Index, elf: *Elf) void { - for (elf.relocs.items[@intFromEnum(si.get(elf).loc_relocs)..]) |*reloc| { - if (reloc.loc != si) break; - reloc.apply(elf); + switch (si.get(elf).loc_relocs) { + .none => {}, + else => |loc_relocs| for (elf.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| { + if (reloc.loc != si) break; + reloc.apply(elf); + }, } } @@ -390,7 +386,7 @@ pub const Reloc = extern struct { target_value, target_endian, ), - .PC32 => std.mem.writeInt( + .PC32, .PLT32 => std.mem.writeInt( i32, loc_slice[0..4], @intCast(@as(i64, @bitCast(target_value -% loc_value))), @@ -402,6 +398,12 @@ pub const Reloc = extern struct { @intCast(target_value), target_endian, ), + .@"32S" => std.mem.writeInt( + i32, + loc_slice[0..4], + @intCast(@as(i64, @bitCast(target_value))), + target_endian, + ), .TPOFF32 => { const phdr = @field(elf.phdrSlice(), @tagName(class)); const ph = &phdr[elf.getNode(elf.known.tls).segment]; @@ -632,12 +634,7 @@ fn initHeaders( switch (class) { .NONE, _ => unreachable, inline else => |ct_class| { - const ElfN = switch (ct_class) { - .NONE, _ => comptime unreachable, - .@"32" => std.elf.Elf32, - .@"64" => std.elf.Elf64, - }; - + const ElfN = ct_class.ElfN(); assert(Node.Known.ehdr == try elf.mf.addOnlyChildNode(gpa, Node.Known.rodata, .{ .size = @sizeOf(ElfN.Ehdr), .alignment = addr_align, @@ -717,11 +714,7 @@ fn initHeaders( switch (class) { .NONE, _ => unreachable, inline else => |ct_class| { - const ElfN = switch (ct_class) { - .NONE, _ => comptime unreachable, - .@"32" => std.elf.Elf32, - .@"64" => std.elf.Elf64, - }; + const ElfN = ct_class.ElfN(); const target_endian = elf.targetEndian(); const phdr: []ElfN.Phdr = @ptrCast(@alignCast(Node.Known.phdr.slice(&elf.mf))); @@ -1293,23 +1286,32 @@ pub fn loadInput(elf: *Elf, input: link.Input) !void { switch (elf.identClass()) { .NONE, _ => unreachable, inline else => |class| { - const ehdr = try r.peekStruct( - @typeInfo(@FieldType(EhdrPtr, @tagName(class))).pointer.child, - target_endian, - ); + const ElfN = class.ElfN(); + const ehdr = try r.peekStruct(ElfN.Ehdr, target_endian); if (ehdr.type != .REL) return diags.failParse(object.path, "unsupported object type", .{}); if (ehdr.machine != elf.ehdrField(.machine)) return diags.failParse(object.path, "wrong machine", .{}); if (ehdr.shoff == 0 or ehdr.shnum <= 1) return; - if (ehdr.shstrndx == std.elf.SHN_UNDEF or ehdr.shstrndx >= ehdr.shnum) - return diags.failParse(object.path, "missing section names", .{}); - const Shdr = @typeInfo(@FieldType(ShdrSlice, @tagName(class))).pointer.child; - if (ehdr.shentsize < @sizeOf(Shdr)) + if (ehdr.shentsize < @sizeOf(ElfN.Shdr)) return diags.failParse(object.path, "unsupported shentsize", .{}); + const sections = try gpa.alloc(struct { + shdr: ElfN.Shdr, + si: Symbol.Index, + }, ehdr.shnum); + defer gpa.free(sections); + try fr.seekTo(ehdr.shoff); + for (sections) |*section| { + section.* = .{ + .shdr = try r.peekStruct(ElfN.Shdr, target_endian), + .si = .null, + }; + try fr.seekBy(ehdr.shentsize); + } const shstrtab = shstrtab: { - try fr.seekTo(ehdr.shoff + ehdr.shentsize * ehdr.shstrndx); - const shdr = try r.peekStruct(Shdr, target_endian); + if (ehdr.shstrndx == std.elf.SHN_UNDEF or ehdr.shstrndx >= ehdr.shnum) + return diags.failParse(object.path, "missing section names", .{}); + const shdr = §ions[ehdr.shstrndx].shdr; if (shdr.type != std.elf.SHT_STRTAB) return diags.failParse(object.path, "invalid shstrtab type", .{}); const shstrtab = try gpa.alloc(u8, @intCast(shdr.size)); @@ -1319,141 +1321,186 @@ pub fn loadInput(elf: *Elf, input: link.Input) !void { break :shstrtab shstrtab; }; defer gpa.free(shstrtab); - const Sym = @typeInfo(@FieldType(SymPtr, @tagName(class))).pointer.child; - try fr.seekTo(ehdr.shoff + ehdr.shentsize); - var symtab_shdr: Shdr = .{ - .name = 0, - .type = 0, - .flags = .{ .shf = .{} }, - .addr = 0, - .offset = 0, - .size = 0, - .link = 0, - .info = 0, - .addralign = 0, - .entsize = 0, + try elf.nodes.ensureUnusedCapacity(gpa, ehdr.shnum - 1); + try elf.symtab.ensureUnusedCapacity(gpa, ehdr.shnum - 1); + try elf.input_sections.ensureUnusedCapacity(gpa, ehdr.shnum - 1); + for (sections[1..]) |*section| switch (section.shdr.type) { + else => {}, + std.elf.SHT_PROGBITS, std.elf.SHT_NOBITS => { + if (section.shdr.name >= shstrtab.len) continue; + const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0); + const parent_si = namedSection(name) orelse continue; + const ni = try elf.mf.addLastChildNode(gpa, parent_si.node(elf), .{ + .size = section.shdr.size, + .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert( + usize, + @intCast(@max(section.shdr.addralign, 1)), + )), + .moved = true, + }); + elf.nodes.appendAssumeCapacity(.{ + .input_section = @enumFromInt(elf.input_sections.items.len), + }); + section.si = try elf.initSymbolAssumeCapacity(.{ + .type = .SECTION, + .shndx = elf.targetLoad(&@field(elf.symPtr(parent_si), @tagName(class)).shndx), + }); + section.si.get(elf).ni = ni; + elf.input_sections.addOneAssumeCapacity().* = .{ + .ii = ii, + .si = section.si, + .file_location = .{ + .offset = section.shdr.offset, + .size = section.shdr.size, + }, + }; + elf.synth_prog_node.increaseEstimatedTotalItems(1); + }, }; - const sections = try gpa.alloc(struct { - shndx: u32, - ni: MappedFile.Node.Index, - }, ehdr.shnum); - defer gpa.free(sections); - @memset(sections, .{ .shndx = std.elf.SHN_UNDEF, .ni = .none }); - for (sections[1..ehdr.shnum]) |*section| { - const shdr = try r.peekStruct(Shdr, target_endian); - try r.discardAll(ehdr.shentsize); - switch (shdr.type) { - else => continue, - std.elf.SHT_PROGBITS => {}, - std.elf.SHT_SYMTAB => { - if (symtab_shdr.entsize > 0) - return diags.failParse(object.path, "too many symtabs", .{}); - if (shdr.entsize < @sizeOf(Sym)) - return diags.failParse(object.path, "unsupported entsize", .{}); - symtab_shdr = shdr; - continue; - }, - std.elf.SHT_NOBITS => {}, - } - if (shdr.name >= shstrtab.len) continue; - const name = std.mem.sliceTo(shstrtab[shdr.name..], 0); - const si = namedSection(name) orelse continue; - section.shndx = elf.targetLoad(&@field(elf.symPtr(si), @tagName(class)).shndx); - try elf.nodes.ensureUnusedCapacity(gpa, 1); - try elf.input_sections.ensureUnusedCapacity(gpa, 1); - section.ni = try elf.mf.addLastChildNode(gpa, si.node(elf), .{ - .size = shdr.size, - .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert( - usize, - @intCast(@max(shdr.addralign, 1)), - )), - .moved = true, - }); - elf.nodes.appendAssumeCapacity(.{ - .input_section = @enumFromInt(elf.input_sections.items.len), - }); - elf.input_sections.addOneAssumeCapacity().* = .{ - .ii = ii, - .ni = section.ni, - .addr = shdr.addr, - .offset = shdr.offset, - .size = shdr.size, - }; - elf.synth_prog_node.increaseEstimatedTotalItems(1); - } - if (symtab_shdr.info <= 1) return; - const strtab = strtab: { - try fr.seekTo(ehdr.shoff + ehdr.shentsize * symtab_shdr.link); - const shdr = try r.peekStruct(Shdr, target_endian); - if (shdr.type != std.elf.SHT_STRTAB) - return diags.failParse(object.path, "invalid strtab type", .{}); - const strtab = try gpa.alloc(u8, @intCast(shdr.size)); - errdefer gpa.free(strtab); - try fr.seekTo(shdr.offset); - try r.readSliceAll(strtab); - break :strtab strtab; - }; - defer gpa.free(strtab); - try elf.symtab.ensureUnusedCapacity(gpa, symtab_shdr.info); - try elf.globals.ensureUnusedCapacity(gpa, symtab_shdr.info); - try fr.seekTo(symtab_shdr.offset + symtab_shdr.entsize); - for (1..try std.math.divExact( - usize, - @intCast(symtab_shdr.size), - @intCast(symtab_shdr.entsize), - )) |_| { - const input_sym = try r.peekStruct(Sym, target_endian); - try r.discardAll64(symtab_shdr.entsize); - switch (input_sym.info.type) { - else => continue, - .OBJECT, .FUNC => {}, - } - if (input_sym.name >= strtab.len or input_sym.shndx >= sections.len) continue; - const name = std.mem.sliceTo(strtab[input_sym.name..], 0); - const si = try elf.initSymbolAssumeCapacity(.{ - .name = name, - .value = input_sym.value, - .size = input_sym.size, - .type = input_sym.info.type, - .bind = input_sym.info.bind, - .visibility = input_sym.other.visibility, - .shndx = @intCast(sections[input_sym.shndx].shndx), - }); - { - const sym = si.get(elf); - sym.ni = sections[input_sym.shndx].ni; - assert(sym.loc_relocs == .none); - sym.loc_relocs = @enumFromInt(elf.relocs.items.len); - } - switch (input_sym.info.bind) { - else => {}, - .GLOBAL => { - const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( - &@field(elf.symPtr(si), @tagName(class)).name, - )); - if (gop.found_existing) switch (elf.targetLoad( - switch (elf.symPtr(gop.value_ptr.*)) { - inline else => |sym| &sym.info, + var symmap: std.ArrayList(Symbol.Index) = .empty; + defer symmap.deinit(gpa); + for (sections[1..], 1..) |*symtab, symtab_shndx| switch (symtab.shdr.type) { + else => {}, + std.elf.SHT_SYMTAB => { + if (symtab.shdr.entsize < @sizeOf(ElfN.Sym)) + return diags.failParse(object.path, "unsupported symtab entsize", .{}); + const strtab = strtab: { + if (symtab.shdr.link == std.elf.SHN_UNDEF or symtab.shdr.link >= ehdr.shnum) + return diags.failParse(object.path, "missing symbol names", .{}); + const shdr = §ions[symtab.shdr.link].shdr; + if (shdr.type != std.elf.SHT_STRTAB) + return diags.failParse(object.path, "invalid strtab type", .{}); + const strtab = try gpa.alloc(u8, @intCast(shdr.size)); + errdefer gpa.free(strtab); + try fr.seekTo(shdr.offset); + try r.readSliceAll(strtab); + break :strtab strtab; + }; + defer gpa.free(strtab); + const symnum = try std.math.divExact( + u32, + @intCast(symtab.shdr.size), + @intCast(symtab.shdr.entsize), + ); + symmap.clearRetainingCapacity(); + try symmap.resize(gpa, symnum); + try elf.symtab.ensureUnusedCapacity(gpa, symnum); + try elf.globals.ensureUnusedCapacity(gpa, symnum); + try fr.seekTo(symtab.shdr.offset + symtab.shdr.entsize); + symmap.items[0] = .null; + for (symmap.items[1..]) |*si| { + si.* = .null; + const input_sym = try r.peekStruct(ElfN.Sym, target_endian); + try fr.seekBy(@intCast(symtab.shdr.entsize)); + if (input_sym.name >= strtab.len or + input_sym.shndx == std.elf.SHN_UNDEF or + input_sym.shndx >= ehdr.shnum) continue; + switch (input_sym.info.type) { + else => continue, + .SECTION => { + const section = §ions[input_sym.shndx]; + if (input_sym.value == section.shdr.addr) si.* = section.si; + continue; }, - ).bind) { - else => unreachable, - .GLOBAL => return diags.failParse( + .OBJECT, .FUNC => {}, + } + const name = std.mem.sliceTo(strtab[input_sym.name..], 0); + const parent_si = sections[input_sym.shndx].si; + si.* = try elf.initSymbolAssumeCapacity(.{ + .name = name, + .value = input_sym.value, + .size = input_sym.size, + .type = input_sym.info.type, + .bind = input_sym.info.bind, + .visibility = input_sym.other.visibility, + .shndx = elf.targetLoad(switch (elf.symPtr(parent_si)) { + inline else => |parent_sym| &parent_sym.shndx, + }), + }); + si.get(elf).ni = parent_si.get(elf).ni; + switch (input_sym.info.bind) { + else => {}, + .GLOBAL => { + const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( + &@field(elf.symPtr(si.*), @tagName(class)).name, + )); + if (gop.found_existing) switch (elf.targetLoad( + switch (elf.symPtr(gop.value_ptr.*)) { + inline else => |sym| &sym.info, + }, + ).bind) { + else => unreachable, + .GLOBAL => return diags.failParse( + object.path, + "multiple definitions of '{s}'", + .{name}, + ), + .WEAK => {}, + }; + gop.value_ptr.* = si.*; + }, + .WEAK => { + const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( + &@field(elf.symPtr(si.*), @tagName(class)).name, + )); + if (!gop.found_existing) gop.value_ptr.* = si.*; + }, + } + } + for (sections[1..]) |*rels| switch (rels.shdr.type) { + else => {}, + inline std.elf.SHT_REL, std.elf.SHT_RELA => |sht| { + if (rels.shdr.link != symtab_shndx or + rels.shdr.info == std.elf.SHN_UNDEF or + rels.shdr.info >= ehdr.shnum) continue; + const Rel = switch (sht) { + else => comptime unreachable, + std.elf.SHT_REL => ElfN.Rel, + std.elf.SHT_RELA => ElfN.Rela, + }; + if (rels.shdr.entsize < @sizeOf(Rel)) return diags.failParse( object.path, - "multiple definitions of '{s}'", - .{name}, - ), - .WEAK => {}, - }; - gop.value_ptr.* = si; - }, - .WEAK => { - const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( - &@field(elf.symPtr(si), @tagName(class)).name, - )); - if (!gop.found_existing) gop.value_ptr.* = si; - }, - } - } + "unsupported rel entsize", + .{}, + ); + const loc_sec = §ions[rels.shdr.info]; + if (loc_sec.si == .null) continue; + const relnum = try std.math.divExact( + u32, + @intCast(rels.shdr.size), + @intCast(rels.shdr.entsize), + ); + try elf.relocs.ensureUnusedCapacity(gpa, relnum); + try fr.seekTo(rels.shdr.offset); + for (0..relnum) |_| { + const rel = try r.peekStruct(Rel, target_endian); + try fr.seekBy(@intCast(rels.shdr.entsize)); + if (rel.info.sym >= symnum) continue; + const target_si = symmap.items[rel.info.sym]; + if (target_si == .null) continue; + elf.addReloc( + loc_sec.si, + rel.offset - loc_sec.shdr.addr, + target_si, + rel.addend, + switch (elf.ehdrField(.machine)) { + else => unreachable, + inline .AARCH64, + .PPC64, + .RISCV, + .X86_64, + => |machine| @unionInit( + Reloc.Type, + @tagName(machine), + @enumFromInt(rel.info.type), + ), + }, + ) catch unreachable; + } + }, + }; + }, + }; }, } }, @@ -1512,11 +1559,10 @@ pub fn getVAddr(elf: *Elf, reloc_info: link.File.RelocInfo, target_si: Symbol.In reloc_info.addend, switch (elf.ehdrField(.machine)) { else => unreachable, - .X86_64 => .{ .X86_64 = switch (elf.identClass()) { - .NONE, _ => unreachable, - .@"32" => .@"32", - .@"64" => .@"64", - } }, + .AARCH64 => .{ .AARCH64 = .ABS64 }, + .PPC64 => .{ .PPC64 = .ADDR64 }, + .RISCV => .{ .RISCV = .@"64" }, + .X86_64 => .{ .X86_64 = .@"64" }, }, ); return switch (elf.symPtr(target_si)) { @@ -1912,11 +1958,11 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { if (elf.input_section_pending_index < elf.input_sections.items.len) { const isi: Node.InputSectionIndex = @enumFromInt(elf.input_section_pending_index); elf.input_section_pending_index += 1; - const sub_prog_node = elf.idleProgNode(tid, elf.input_prog_node, elf.getNode(isi.node(elf))); + const sub_prog_node = elf.idleProgNode(tid, elf.input_prog_node, elf.getNode(isi.symbol(elf).node(elf))); defer sub_prog_node.end(); elf.flushInputSection(isi) catch |err| switch (err) { else => |e| return comp.link_diags.fail("linker failed to read input section '{s}' from \"{f}\": {t}", .{ - elf.sectionName(elf.getNode(isi.node(elf).parent(&elf.mf)).section), + elf.sectionName(elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section), isi.input(elf).path(elf).fmtEscapeString(), e, }), @@ -1954,7 +2000,7 @@ fn idleProgNode( .section => |si| elf.sectionName(si), .input_section => |isi| std.fmt.bufPrint(&name, "{f} {s}", .{ isi.input(elf).path(elf), - elf.sectionName(elf.getNode(isi.node(elf).parent(&elf.mf)).section), + elf.sectionName(elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section), }) catch &name, .nav => |nmi| { const ip = &elf.base.comp.zcu.?.intern_pool; @@ -2078,8 +2124,8 @@ fn flushLazy(elf: *Elf, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void { } fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { - const offset, const size = isi.fileLocation(elf); - if (size == 0) return; + const file_loc = isi.fileLocation(elf); + if (file_loc.size == 0) return; const comp = elf.base.comp; const gpa = comp.gpa; const io = comp.io; @@ -2087,11 +2133,11 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { const file = try path.root_dir.handle.adaptToNewApi().openFile(io, path.sub_path, .{}); defer file.close(io); var fr = file.reader(io, &.{}); - try fr.seekTo(offset); + try fr.seekTo(file_loc.offset); var nw: MappedFile.Node.Writer = undefined; - isi.node(elf).writer(&elf.mf, gpa, &nw); + isi.symbol(elf).node(elf).writer(&elf.mf, gpa, &nw); defer nw.deinit(); - if (try nw.interface.sendFileAll(&fr, .limited(@intCast(size))) != size) return error.EndOfStream; + if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; } fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { @@ -2132,13 +2178,13 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { }, }, .input_section => |isi| { - const addr = isi.addr(elf); - const old_addr = addr.*; + const old_addr = switch (elf.symPtr(isi.symbol(elf))) { + inline else => |sym| elf.targetLoad(&sym.value), + }; const new_addr = elf.computeNodeVAddr(ni); - addr.* = new_addr; const ii = isi.input(elf); var si = ii.symbol(elf); - const end_si = ii.nextSymbol(elf); + const end_si = ii.endSymbol(elf); while (cond: { si = si.next(); break :cond si != end_si; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 7550638197..7d2f153a59 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -144,6 +144,8 @@ pub const Node = extern struct { } }; + pub const FileLocation = struct { offset: u64, size: u64 }; + pub const Index = enum(u32) { none, _, @@ -275,7 +277,7 @@ pub const Node = extern struct { ni: Node.Index, mf: *const MappedFile, set_has_content: bool, - ) struct { offset: u64, size: u64 } { + ) FileLocation { var offset, const size = ni.location(mf).resolve(mf); var parent_ni = ni; while (true) { @@ -402,8 +404,6 @@ pub const Node = extern struct { file_reader.interface.toss(n); return n; } - - assert(file_reader.logicalPos() == file_reader.pos); const w: *Writer = @fieldParentPtr("interface", interface); const n: usize = @intCast(w.mf.copyFileRange( file_reader.file, From c4478e078b88c836b53d0e42065b0f68e62c0add Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Oct 2025 22:24:26 -0400 Subject: [PATCH 3/5] Elf2: load archives --- lib/std/Io/File.zig | 13 +- src/Compilation.zig | 1 - src/link/Elf2.zig | 669 ++++++++++++++++++++++++---------------- src/link/MappedFile.zig | 9 +- 4 files changed, 421 insertions(+), 271 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index cc4ce64a22..a83f8e62a2 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -434,8 +434,7 @@ pub const Reader = struct { return err; }; } - r.interface.seek = 0; - r.interface.end = 0; + r.interface.tossBuffered(); }, .failure => return r.seek_err.?, } @@ -467,15 +466,11 @@ pub const Reader = struct { } fn setLogicalPos(r: *Reader, offset: u64) void { - const logical_pos = logicalPos(r); + const logical_pos = r.logicalPos(); if (offset < logical_pos or offset >= r.pos) { - r.interface.seek = 0; - r.interface.end = 0; + r.interface.tossBuffered(); r.pos = offset; - } else { - const logical_delta: usize = @intCast(offset - logical_pos); - r.interface.seek += logical_delta; - } + } else r.interface.toss(@intCast(offset - logical_pos)); } /// Number of slices to store on the stack, when trying to send as many byte diff --git a/src/Compilation.zig b/src/Compilation.zig index 488f7db675..64721552bc 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1989,7 +1989,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, break :s if (is_exe_or_dyn_lib and build_options.have_llvm) .dyn_lib else .zcu; }, } - if (options.config.use_new_linker) break :s .obj; } if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm if (is_exe_or_dyn_lib) break :s .lib; diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 162fe99796..687aa5963b 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -8,12 +8,13 @@ shstrtab: StringTable, strtab: StringTable, inputs: std.ArrayList(struct { path: std.Build.Cache.Path, + member: ?[]const u8, si: Symbol.Index, }), input_sections: std.ArrayList(struct { ii: Node.InputIndex, - si: Symbol.Index, file_location: MappedFile.Node.FileLocation, + si: Symbol.Index, }), input_section_pending_index: u32, globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index), @@ -53,6 +54,10 @@ pub const Node = union(enum) { return elf.inputs.items[@intFromEnum(ii)].path; } + pub fn member(ii: InputIndex, elf: *const Elf) ?[]const u8 { + return elf.inputs.items[@intFromEnum(ii)].member; + } + pub fn symbol(ii: InputIndex, elf: *const Elf) Symbol.Index { return elf.inputs.items[@intFromEnum(ii)].si; } @@ -73,13 +78,13 @@ pub const Node = union(enum) { return elf.input_sections.items[@intFromEnum(isi)].ii; } - pub fn symbol(isi: InputSectionIndex, elf: *const Elf) Symbol.Index { - return elf.input_sections.items[@intFromEnum(isi)].si; - } - pub fn fileLocation(isi: InputSectionIndex, elf: *const Elf) MappedFile.Node.FileLocation { return elf.input_sections.items[@intFromEnum(isi)].file_location; } + + pub fn symbol(isi: InputSectionIndex, elf: *const Elf) Symbol.Index { + return elf.input_sections.items[@intFromEnum(isi)].si; + } }; pub const NavMapIndex = enum(u32) { @@ -571,6 +576,7 @@ pub fn deinit(elf: *Elf) void { elf.symtab.deinit(gpa); elf.shstrtab.map.deinit(gpa); elf.strtab.map.deinit(gpa); + for (elf.inputs.items) |input| if (input.member) |m| gpa.free(m); elf.inputs.deinit(gpa); elf.input_sections.deinit(gpa); elf.globals.deinit(gpa); @@ -1255,256 +1261,370 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index { return lazy_gop.value_ptr.*; } -pub fn loadInput(elf: *Elf, input: link.Input) !void { +pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || + std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, LinkFailure })!void { + const io = elf.base.comp.io; + var buf: [4096]u8 = undefined; + switch (input) { + else => {}, + .object => |object| { + var fsr: FileSliceReader = .init(object.file.reader(io, &.{})); + fsr.reset(try fsr.file.getSize(), &buf); + elf.loadObject(object.path, null, &fsr) catch |err| switch (err) { + error.ReadFailed => return fsr.file.err.?, + else => |e| return e, + }; + }, + .archive => |archive| { + var fsr: FileSliceReader = .init(archive.file.reader(io, &buf)); + elf.loadArchive(archive.path, &fsr) catch |err| switch (err) { + error.ReadFailed => return fsr.file.err.?, + else => |e| return e, + }; + }, + } +} +const FileSliceReader = struct { + file: std.Io.File.Reader, + file_location: MappedFile.Node.FileLocation, + reader: std.Io.Reader.Limited, + + pub fn init(file: std.Io.File.Reader) FileSliceReader { + return .{ .file = file, .file_location = undefined, .reader = undefined }; + } + + pub fn reset(fsr: *FileSliceReader, size: u64, buffer: []u8) void { + fsr.file_location = .{ + .offset = fsr.file.logicalPos(), + .size = size, + }; + fsr.reader = .init(&fsr.file.interface, .limited(@intCast(size)), buffer); + } + + pub fn pos(fsr: *const FileSliceReader) u64 { + return fsr.file.logicalPos() - fsr.file_location.offset; + } + + pub fn logicalPos(fsr: *const FileSliceReader) u64 { + return fsr.pos() - fsr.reader.interface.bufferedLen(); + } + + pub fn seekTo(fsr: *FileSliceReader, offset: u64) std.Io.File.Reader.SeekError!void { + if (offset > fsr.file_location.size) return error.EndOfStream; + const logical_pos = fsr.logicalPos(); + if (offset < logical_pos or offset >= fsr.pos()) { + fsr.reader.interface.tossBuffered(); + try fsr.file.seekTo(fsr.file_location.offset + offset); + fsr.reader.remaining = .limited(@intCast(fsr.file_location.size - offset)); + } else fsr.reader.interface.toss(@intCast(offset - logical_pos)); + } +}; +fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fsr: *FileSliceReader) !void { const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; - switch (input) { - .object => |object| { - const ii: Node.InputIndex = @enumFromInt(elf.inputs.items.len); - log.debug("loadInput(.{{ .object = {f} }}) = {d}", .{ object.path, ii }); - { - try elf.symtab.ensureUnusedCapacity(gpa, 1); - try elf.inputs.ensureUnusedCapacity(gpa, 1); - const si = try elf.initSymbolAssumeCapacity(.{ - .name = std.fs.path.stem(object.path.sub_path), - .type = .FILE, - .shndx = std.elf.SHN_ABS, - }); - elf.inputs.addOneAssumeCapacity().* = .{ - .path = object.path, - .si = si, - }; - } + const r = &fsr.file.interface; + + log.debug("loadArchive({f})", .{path.fmtEscapeString()}); + if (!std.mem.eql(u8, try r.take(std.elf.ARMAG.len), std.elf.ARMAG)) + return diags.failParse(path, "bad magic", .{}); + var strtab: std.Io.Writer.Allocating = .init(gpa); + defer strtab.deinit(); + while (r.takeStruct(std.elf.ar_hdr, native_endian)) |header| { + if (!std.mem.eql(u8, &header.ar_fmag, std.elf.ARFMAG)) + return diags.failParse(path, "bad file magic", .{}); + const size = header.size() catch + return diags.failParse(path, "bad member size", .{}); + if (std.mem.eql(u8, &header.ar_name, std.elf.STRNAME)) { + strtab.clearRetainingCapacity(); + try strtab.ensureTotalCapacityPrecise(size); + r.streamExact(&strtab.writer, size) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + else => |e| return e, + }; + continue; + } + load_object: { + const member = header.name() orelse member: { + const strtab_offset = header.nameOffset() catch |err| switch (err) { + error.Overflow => break :member error.Overflow, + error.InvalidCharacter => break :load_object, + } orelse break :load_object; + const strtab_written = strtab.written(); + if (strtab_offset > strtab_written.len) break :member error.Overflow; + const member = std.mem.sliceTo(strtab_written[strtab_offset..], '\n'); + break :member if (std.mem.endsWith(u8, member, "/")) + member[0 .. member.len - "/".len] + else + member; + } catch |err| switch (err) { + error.Overflow => return diags.failParse(path, "bad member name offset", .{}), + }; + if (!std.mem.endsWith(u8, member, ".o")) break :load_object; var buf: [4096]u8 = undefined; - var fr = object.file.reader(comp.io, &buf); - const r = &fr.interface; - const ident = try r.peek(std.elf.EI.NIDENT); - if (!std.mem.eql(u8, elf.mf.contents[0..std.elf.EI.NIDENT], ident)) - return diags.failParse(object.path, "wrong ident", .{}); - const target_endian = elf.targetEndian(); - switch (elf.identClass()) { - .NONE, _ => unreachable, - inline else => |class| { - const ElfN = class.ElfN(); - const ehdr = try r.peekStruct(ElfN.Ehdr, target_endian); - if (ehdr.type != .REL) - return diags.failParse(object.path, "unsupported object type", .{}); - if (ehdr.machine != elf.ehdrField(.machine)) - return diags.failParse(object.path, "wrong machine", .{}); - if (ehdr.shoff == 0 or ehdr.shnum <= 1) return; - if (ehdr.shentsize < @sizeOf(ElfN.Shdr)) - return diags.failParse(object.path, "unsupported shentsize", .{}); - const sections = try gpa.alloc(struct { - shdr: ElfN.Shdr, - si: Symbol.Index, - }, ehdr.shnum); - defer gpa.free(sections); - try fr.seekTo(ehdr.shoff); - for (sections) |*section| { - section.* = .{ - .shdr = try r.peekStruct(ElfN.Shdr, target_endian), - .si = .null, - }; - try fr.seekBy(ehdr.shentsize); - } - const shstrtab = shstrtab: { - if (ehdr.shstrndx == std.elf.SHN_UNDEF or ehdr.shstrndx >= ehdr.shnum) - return diags.failParse(object.path, "missing section names", .{}); - const shdr = §ions[ehdr.shstrndx].shdr; - if (shdr.type != std.elf.SHT_STRTAB) - return diags.failParse(object.path, "invalid shstrtab type", .{}); - const shstrtab = try gpa.alloc(u8, @intCast(shdr.size)); - errdefer gpa.free(shstrtab); - try fr.seekTo(shdr.offset); - try r.readSliceAll(shstrtab); - break :shstrtab shstrtab; - }; - defer gpa.free(shstrtab); - try elf.nodes.ensureUnusedCapacity(gpa, ehdr.shnum - 1); - try elf.symtab.ensureUnusedCapacity(gpa, ehdr.shnum - 1); - try elf.input_sections.ensureUnusedCapacity(gpa, ehdr.shnum - 1); - for (sections[1..]) |*section| switch (section.shdr.type) { - else => {}, - std.elf.SHT_PROGBITS, std.elf.SHT_NOBITS => { - if (section.shdr.name >= shstrtab.len) continue; - const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0); - const parent_si = namedSection(name) orelse continue; - const ni = try elf.mf.addLastChildNode(gpa, parent_si.node(elf), .{ - .size = section.shdr.size, - .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert( - usize, - @intCast(@max(section.shdr.addralign, 1)), - )), - .moved = true, - }); - elf.nodes.appendAssumeCapacity(.{ - .input_section = @enumFromInt(elf.input_sections.items.len), - }); - section.si = try elf.initSymbolAssumeCapacity(.{ - .type = .SECTION, - .shndx = elf.targetLoad(&@field(elf.symPtr(parent_si), @tagName(class)).shndx), - }); - section.si.get(elf).ni = ni; - elf.input_sections.addOneAssumeCapacity().* = .{ - .ii = ii, - .si = section.si, - .file_location = .{ - .offset = section.shdr.offset, - .size = section.shdr.size, - }, - }; - elf.synth_prog_node.increaseEstimatedTotalItems(1); + fsr.reset(size, &buf); + try elf.loadObject(path, member, fsr); + try fsr.seekTo(size); + continue; + } + try r.discardAll(size); + } else |err| switch (err) { + error.EndOfStream => if (!fsr.file.atEnd()) return error.EndOfStream, + else => |e| return e, + } +} +fn fmtMemberString(member: ?[]const u8) std.fmt.Alt(?[]const u8, memberStringEscape) { + return .{ .data = member }; +} +fn memberStringEscape(member: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { + try w.print("({f})", .{std.zig.fmtString(member orelse return)}); +} +fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: *FileSliceReader) !void { + const comp = elf.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const r = &fsr.reader.interface; + + const ii: Node.InputIndex = @enumFromInt(elf.inputs.items.len); + log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) }); + const ident = try r.peek(std.elf.EI.NIDENT); + if (!std.mem.eql(u8, ident, elf.mf.contents[0..std.elf.EI.NIDENT])) + return diags.failParse(path, "bad ident", .{}); + try elf.symtab.ensureUnusedCapacity(gpa, 1); + try elf.inputs.ensureUnusedCapacity(gpa, 1); + elf.inputs.addOneAssumeCapacity().* = .{ + .path = path, + .member = if (member) |m| try gpa.dupe(u8, m) else null, + .si = try elf.initSymbolAssumeCapacity(.{ + .name = std.fs.path.stem(member orelse path.sub_path), + .type = .FILE, + .shndx = std.elf.SHN_ABS, + }), + }; + const target_endian = elf.targetEndian(); + switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| { + const ElfN = class.ElfN(); + const ehdr = try r.peekStruct(ElfN.Ehdr, target_endian); + if (ehdr.type != .REL) return diags.failParse(path, "unsupported object type", .{}); + if (ehdr.machine != elf.ehdrField(.machine)) + return diags.failParse(path, "bad machine", .{}); + if (ehdr.shoff == 0 or ehdr.shnum <= 1) return; + if (ehdr.shentsize < @sizeOf(ElfN.Shdr)) + return diags.failParse(path, "unsupported shentsize", .{}); + const sections = try gpa.alloc(struct { shdr: ElfN.Shdr, si: Symbol.Index }, ehdr.shnum); + defer gpa.free(sections); + try fsr.seekTo(ehdr.shoff); + for (sections) |*section| { + section.* = .{ + .shdr = try r.peekStruct(ElfN.Shdr, target_endian), + .si = .null, + }; + try r.discardAll(ehdr.shentsize); + switch (section.shdr.type) { + std.elf.SHT_NULL, std.elf.SHT_NOBITS => {}, + else => if (section.shdr.offset + section.shdr.size > fsr.file_location.size) + return diags.failParse(path, "bad section offset/size", .{}), + } + } + const shstrtab = shstrtab: { + if (ehdr.shstrndx == std.elf.SHN_UNDEF or ehdr.shstrndx >= ehdr.shnum) + return diags.failParse(path, "missing section names", .{}); + const shdr = §ions[ehdr.shstrndx].shdr; + if (shdr.type != std.elf.SHT_STRTAB) + return diags.failParse(path, "invalid shstrtab type", .{}); + const shstrtab = try gpa.alloc(u8, @intCast(shdr.size)); + errdefer gpa.free(shstrtab); + try fsr.seekTo(shdr.offset); + try r.readSliceAll(shstrtab); + break :shstrtab shstrtab; + }; + defer gpa.free(shstrtab); + try elf.nodes.ensureUnusedCapacity(gpa, ehdr.shnum - 1); + try elf.symtab.ensureUnusedCapacity(gpa, ehdr.shnum - 1); + try elf.input_sections.ensureUnusedCapacity(gpa, ehdr.shnum - 1); + for (sections[1..]) |*section| switch (section.shdr.type) { + else => {}, + std.elf.SHT_PROGBITS, std.elf.SHT_NOBITS => { + if (section.shdr.name >= shstrtab.len) continue; + const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0); + const parent_si = namedSection(name) orelse continue; + const ni = try elf.mf.addLastChildNode(gpa, parent_si.node(elf), .{ + .size = section.shdr.size, + .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert( + usize, + @intCast(@max(section.shdr.addralign, 1)), + )), + .moved = true, + }); + elf.nodes.appendAssumeCapacity(.{ + .input_section = @enumFromInt(elf.input_sections.items.len), + }); + section.si = try elf.initSymbolAssumeCapacity(.{ + .type = .SECTION, + .shndx = elf.targetLoad(&@field(elf.symPtr(parent_si), @tagName(class)).shndx), + }); + section.si.get(elf).ni = ni; + elf.input_sections.addOneAssumeCapacity().* = .{ + .ii = ii, + .si = section.si, + .file_location = .{ + .offset = fsr.file_location.offset + section.shdr.offset, + .size = section.shdr.size, }, }; - var symmap: std.ArrayList(Symbol.Index) = .empty; - defer symmap.deinit(gpa); - for (sections[1..], 1..) |*symtab, symtab_shndx| switch (symtab.shdr.type) { + elf.synth_prog_node.increaseEstimatedTotalItems(1); + }, + }; + var symmap: std.ArrayList(Symbol.Index) = .empty; + defer symmap.deinit(gpa); + for (sections[1..], 1..) |*symtab, symtab_shndx| switch (symtab.shdr.type) { + else => {}, + std.elf.SHT_SYMTAB => { + if (symtab.shdr.entsize < @sizeOf(ElfN.Sym)) + return diags.failParse(path, "unsupported symtab entsize", .{}); + const strtab = strtab: { + if (symtab.shdr.link == std.elf.SHN_UNDEF or symtab.shdr.link >= ehdr.shnum) + return diags.failParse(path, "missing symbol names", .{}); + const shdr = §ions[symtab.shdr.link].shdr; + if (shdr.type != std.elf.SHT_STRTAB) + return diags.failParse(path, "invalid strtab type", .{}); + const strtab = try gpa.alloc(u8, @intCast(shdr.size)); + errdefer gpa.free(strtab); + try fsr.seekTo(shdr.offset); + try r.readSliceAll(strtab); + break :strtab strtab; + }; + defer gpa.free(strtab); + const symnum = std.math.divExact( + u32, + @intCast(symtab.shdr.size), + @intCast(symtab.shdr.entsize), + ) catch return diags.failParse( + path, + "symtab section size (0x{x}) is not a multiple of entsize (0x{x})", + .{ symtab.shdr.size, symtab.shdr.entsize }, + ); + symmap.clearRetainingCapacity(); + try symmap.resize(gpa, symnum); + try elf.symtab.ensureUnusedCapacity(gpa, symnum); + try elf.globals.ensureUnusedCapacity(gpa, symnum); + try fsr.seekTo(symtab.shdr.offset + symtab.shdr.entsize); + symmap.items[0] = .null; + for (symmap.items[1..]) |*si| { + si.* = .null; + const input_sym = try r.peekStruct(ElfN.Sym, target_endian); + try r.discardAll64(symtab.shdr.entsize); + if (input_sym.name >= strtab.len or input_sym.shndx == std.elf.SHN_UNDEF or + input_sym.shndx >= ehdr.shnum) continue; + switch (input_sym.info.type) { + else => continue, + .SECTION => { + const section = §ions[input_sym.shndx]; + if (input_sym.value == section.shdr.addr) si.* = section.si; + continue; + }, + .OBJECT, .FUNC => {}, + } + const name = std.mem.sliceTo(strtab[input_sym.name..], 0); + const parent_si = sections[input_sym.shndx].si; + si.* = try elf.initSymbolAssumeCapacity(.{ + .name = name, + .value = input_sym.value, + .size = input_sym.size, + .type = input_sym.info.type, + .bind = input_sym.info.bind, + .visibility = input_sym.other.visibility, + .shndx = elf.targetLoad(switch (elf.symPtr(parent_si)) { + inline else => |parent_sym| &parent_sym.shndx, + }), + }); + si.get(elf).ni = parent_si.get(elf).ni; + switch (input_sym.info.bind) { + else => {}, + .GLOBAL => { + const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( + &@field(elf.symPtr(si.*), @tagName(class)).name, + )); + if (gop.found_existing) switch (elf.targetLoad( + switch (elf.symPtr(gop.value_ptr.*)) { + inline else => |sym| &sym.info, + }, + ).bind) { + else => unreachable, + .GLOBAL => return diags.failParse( + path, + "multiple definitions of '{s}'", + .{name}, + ), + .WEAK => {}, + }; + gop.value_ptr.* = si.*; + }, + .WEAK => { + const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( + &@field(elf.symPtr(si.*), @tagName(class)).name, + )); + if (!gop.found_existing) gop.value_ptr.* = si.*; + }, + } + } + for (sections[1..]) |*rels| switch (rels.shdr.type) { else => {}, - std.elf.SHT_SYMTAB => { - if (symtab.shdr.entsize < @sizeOf(ElfN.Sym)) - return diags.failParse(object.path, "unsupported symtab entsize", .{}); - const strtab = strtab: { - if (symtab.shdr.link == std.elf.SHN_UNDEF or symtab.shdr.link >= ehdr.shnum) - return diags.failParse(object.path, "missing symbol names", .{}); - const shdr = §ions[symtab.shdr.link].shdr; - if (shdr.type != std.elf.SHT_STRTAB) - return diags.failParse(object.path, "invalid strtab type", .{}); - const strtab = try gpa.alloc(u8, @intCast(shdr.size)); - errdefer gpa.free(strtab); - try fr.seekTo(shdr.offset); - try r.readSliceAll(strtab); - break :strtab strtab; + inline std.elf.SHT_REL, std.elf.SHT_RELA => |sht| { + if (rels.shdr.link != symtab_shndx or rels.shdr.info == std.elf.SHN_UNDEF or + rels.shdr.info >= ehdr.shnum) continue; + const Rel = switch (sht) { + else => comptime unreachable, + std.elf.SHT_REL => ElfN.Rel, + std.elf.SHT_RELA => ElfN.Rela, }; - defer gpa.free(strtab); - const symnum = try std.math.divExact( + if (rels.shdr.entsize < @sizeOf(Rel)) + return diags.failParse(path, "unsupported rel entsize", .{}); + const loc_sec = §ions[rels.shdr.info]; + if (loc_sec.si == .null) continue; + const relnum = std.math.divExact( u32, - @intCast(symtab.shdr.size), - @intCast(symtab.shdr.entsize), + @intCast(rels.shdr.size), + @intCast(rels.shdr.entsize), + ) catch return diags.failParse( + path, + "relocation section size (0x{x}) is not a multiple of entsize (0x{x})", + .{ rels.shdr.size, rels.shdr.entsize }, ); - symmap.clearRetainingCapacity(); - try symmap.resize(gpa, symnum); - try elf.symtab.ensureUnusedCapacity(gpa, symnum); - try elf.globals.ensureUnusedCapacity(gpa, symnum); - try fr.seekTo(symtab.shdr.offset + symtab.shdr.entsize); - symmap.items[0] = .null; - for (symmap.items[1..]) |*si| { - si.* = .null; - const input_sym = try r.peekStruct(ElfN.Sym, target_endian); - try fr.seekBy(@intCast(symtab.shdr.entsize)); - if (input_sym.name >= strtab.len or - input_sym.shndx == std.elf.SHN_UNDEF or - input_sym.shndx >= ehdr.shnum) continue; - switch (input_sym.info.type) { - else => continue, - .SECTION => { - const section = §ions[input_sym.shndx]; - if (input_sym.value == section.shdr.addr) si.* = section.si; - continue; + try elf.relocs.ensureUnusedCapacity(gpa, relnum); + try fsr.seekTo(rels.shdr.offset); + for (0..relnum) |_| { + const rel = try r.peekStruct(Rel, target_endian); + try r.discardAll64(rels.shdr.entsize); + if (rel.info.sym >= symnum) continue; + const target_si = symmap.items[rel.info.sym]; + if (target_si == .null) continue; + elf.addRelocAssumeCapacity( + loc_sec.si, + rel.offset - loc_sec.shdr.addr, + target_si, + rel.addend, + switch (elf.ehdrField(.machine)) { + else => unreachable, + inline .AARCH64, + .PPC64, + .RISCV, + .X86_64, + => |machine| @unionInit( + Reloc.Type, + @tagName(machine), + @enumFromInt(rel.info.type), + ), }, - .OBJECT, .FUNC => {}, - } - const name = std.mem.sliceTo(strtab[input_sym.name..], 0); - const parent_si = sections[input_sym.shndx].si; - si.* = try elf.initSymbolAssumeCapacity(.{ - .name = name, - .value = input_sym.value, - .size = input_sym.size, - .type = input_sym.info.type, - .bind = input_sym.info.bind, - .visibility = input_sym.other.visibility, - .shndx = elf.targetLoad(switch (elf.symPtr(parent_si)) { - inline else => |parent_sym| &parent_sym.shndx, - }), - }); - si.get(elf).ni = parent_si.get(elf).ni; - switch (input_sym.info.bind) { - else => {}, - .GLOBAL => { - const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( - &@field(elf.symPtr(si.*), @tagName(class)).name, - )); - if (gop.found_existing) switch (elf.targetLoad( - switch (elf.symPtr(gop.value_ptr.*)) { - inline else => |sym| &sym.info, - }, - ).bind) { - else => unreachable, - .GLOBAL => return diags.failParse( - object.path, - "multiple definitions of '{s}'", - .{name}, - ), - .WEAK => {}, - }; - gop.value_ptr.* = si.*; - }, - .WEAK => { - const gop = elf.globals.getOrPutAssumeCapacity(elf.targetLoad( - &@field(elf.symPtr(si.*), @tagName(class)).name, - )); - if (!gop.found_existing) gop.value_ptr.* = si.*; - }, - } + ); } - for (sections[1..]) |*rels| switch (rels.shdr.type) { - else => {}, - inline std.elf.SHT_REL, std.elf.SHT_RELA => |sht| { - if (rels.shdr.link != symtab_shndx or - rels.shdr.info == std.elf.SHN_UNDEF or - rels.shdr.info >= ehdr.shnum) continue; - const Rel = switch (sht) { - else => comptime unreachable, - std.elf.SHT_REL => ElfN.Rel, - std.elf.SHT_RELA => ElfN.Rela, - }; - if (rels.shdr.entsize < @sizeOf(Rel)) return diags.failParse( - object.path, - "unsupported rel entsize", - .{}, - ); - const loc_sec = §ions[rels.shdr.info]; - if (loc_sec.si == .null) continue; - const relnum = try std.math.divExact( - u32, - @intCast(rels.shdr.size), - @intCast(rels.shdr.entsize), - ); - try elf.relocs.ensureUnusedCapacity(gpa, relnum); - try fr.seekTo(rels.shdr.offset); - for (0..relnum) |_| { - const rel = try r.peekStruct(Rel, target_endian); - try fr.seekBy(@intCast(rels.shdr.entsize)); - if (rel.info.sym >= symnum) continue; - const target_si = symmap.items[rel.info.sym]; - if (target_si == .null) continue; - elf.addReloc( - loc_sec.si, - rel.offset - loc_sec.shdr.addr, - target_si, - rel.addend, - switch (elf.ehdrField(.machine)) { - else => unreachable, - inline .AARCH64, - .PPC64, - .RISCV, - .X86_64, - => |machine| @unionInit( - Reloc.Type, - @tagName(machine), - @enumFromInt(rel.info.type), - ), - }, - ) catch unreachable; - } - }, - }; }, }; }, - } + }; }, - else => {}, } } @@ -1530,6 +1650,7 @@ fn prelinkInner(elf: *Elf) !void { }); elf.inputs.addOneAssumeCapacity().* = .{ .path = elf.base.emit, + .member = null, .si = si, }; } @@ -1667,10 +1788,20 @@ pub fn addReloc( addend: i64, @"type": Reloc.Type, ) !void { - const gpa = elf.base.comp.gpa; + try elf.relocs.ensureUnusedCapacity(elf.base.comp.gpa, 1); + elf.addRelocAssumeCapacity(loc_si, offset, target_si, addend, @"type"); +} +pub fn addRelocAssumeCapacity( + elf: *Elf, + loc_si: Symbol.Index, + offset: u64, + target_si: Symbol.Index, + addend: i64, + @"type": Reloc.Type, +) void { const target = target_si.get(elf); const ri: Reloc.Index = @enumFromInt(elf.relocs.items.len); - (try elf.relocs.addOne(gpa)).* = .{ + elf.relocs.addOneAssumeCapacity().* = .{ .type = @"type", .prev = .none, .next = target.target_relocs, @@ -1961,11 +2092,20 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { const sub_prog_node = elf.idleProgNode(tid, elf.input_prog_node, elf.getNode(isi.symbol(elf).node(elf))); defer sub_prog_node.end(); elf.flushInputSection(isi) catch |err| switch (err) { - else => |e| return comp.link_diags.fail("linker failed to read input section '{s}' from \"{f}\": {t}", .{ - elf.sectionName(elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section), - isi.input(elf).path(elf).fmtEscapeString(), - e, - }), + else => |e| { + const ii = isi.input(elf); + return comp.link_diags.fail( + "linker failed to read input section '{s}' from \"{f}{f}\": {t}", + .{ + elf.sectionName( + elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section, + ), + ii.path(elf).fmtEscapeString(), + fmtMemberString(ii.member(elf)), + e, + }, + ); + }, }; break :task; } @@ -1998,10 +2138,14 @@ fn idleProgNode( return prog_node.start(name: switch (node) { else => |tag| @tagName(tag), .section => |si| elf.sectionName(si), - .input_section => |isi| std.fmt.bufPrint(&name, "{f} {s}", .{ - isi.input(elf).path(elf), - elf.sectionName(elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section), - }) catch &name, + .input_section => |isi| { + const ii = isi.input(elf); + break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{ + ii.path(elf).fmtEscapeString(), + fmtMemberString(ii.member(elf)), + elf.sectionName(elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section), + }) catch &name; + }, .nav => |nmi| { const ip = &elf.base.comp.zcu.?.intern_pool; break :name ip.getNav(nmi.navIndex(elf)).fqn.toSlice(ip); @@ -2128,16 +2272,17 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { if (file_loc.size == 0) return; const comp = elf.base.comp; const gpa = comp.gpa; - const io = comp.io; - const path = isi.input(elf).path(elf); - const file = try path.root_dir.handle.adaptToNewApi().openFile(io, path.sub_path, .{}); - defer file.close(io); - var fr = file.reader(io, &.{}); + const ii = isi.input(elf); + const path = ii.path(elf); + const file = try path.root_dir.handle.adaptToNewApi().openFile(comp.io, path.sub_path, .{}); + defer file.close(comp.io); + var fr = file.reader(comp.io, &.{}); try fr.seekTo(file_loc.offset); var nw: MappedFile.Node.Writer = undefined; isi.symbol(elf).node(elf).writer(&elf.mf, gpa, &nw); defer nw.deinit(); - if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) return error.EndOfStream; + if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) + return error.EndOfStream; } fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { @@ -2396,10 +2541,14 @@ pub fn printNode( switch (node) { else => {}, .section => |si| try w.print("({s})", .{elf.sectionName(si)}), - .input_section => |isi| try w.print("({f}, {s})", .{ - isi.input(elf).path(elf), - elf.sectionName(elf.getNode(isi.node(elf).parent(&elf.mf)).section), - }), + .input_section => |isi| { + const ii = isi.input(elf); + try w.print("({f}{f}, {s})", .{ + ii.path(elf).fmtEscapeString(), + fmtMemberString(ii.member(elf)), + elf.sectionName(elf.getNode(isi.symbol(elf).node(elf).parent(&elf.mf)).section), + }); + }, .nav => |nmi| { const zcu = elf.base.comp.zcu.?; const ip = &zcu.intern_pool; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 7d2f153a59..8b4d67cf63 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -144,7 +144,14 @@ pub const Node = extern struct { } }; - pub const FileLocation = struct { offset: u64, size: u64 }; + pub const FileLocation = struct { + offset: u64, + size: u64, + + pub fn end(fl: FileLocation) u64 { + return fl.offset + fl.size; + } + }; pub const Index = enum(u32) { none, From 40901440a620caf1849c627cff0d3a96eda273f5 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 29 Oct 2025 18:03:38 -0400 Subject: [PATCH 4/5] Elf2: simplify archive loading --- src/link/Elf/Archive.zig | 4 +- src/link/Elf2.zig | 99 ++++++++++++++-------------------------- 2 files changed, 36 insertions(+), 67 deletions(-) diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig index 0157b18fb4..d1216fcbba 100644 --- a/src/link/Elf/Archive.zig +++ b/src/link/Elf/Archive.zig @@ -34,8 +34,6 @@ pub fn parse( defer strtab.deinit(gpa); while (pos < size) { - pos = mem.alignForward(usize, pos, 2); - var hdr: elf.ar_hdr = undefined; { const n = try handle.preadAll(mem.asBytes(&hdr), pos); @@ -50,7 +48,7 @@ pub fn parse( } const obj_size = try hdr.size(); - defer pos += obj_size; + defer pos = std.mem.alignForward(usize, pos + obj_size, 2); if (hdr.isSymtab() or hdr.isSymtab64()) continue; if (hdr.isStrtab()) { diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 687aa5963b..c985c32fc9 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -1268,62 +1268,29 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || switch (input) { else => {}, .object => |object| { - var fsr: FileSliceReader = .init(object.file.reader(io, &.{})); - fsr.reset(try fsr.file.getSize(), &buf); - elf.loadObject(object.path, null, &fsr) catch |err| switch (err) { - error.ReadFailed => return fsr.file.err.?, + var fr = object.file.reader(io, &buf); + elf.loadObject(object.path, null, &fr, .{ + .offset = fr.logicalPos(), + .size = try fr.getSize(), + }) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, else => |e| return e, }; }, .archive => |archive| { - var fsr: FileSliceReader = .init(archive.file.reader(io, &buf)); - elf.loadArchive(archive.path, &fsr) catch |err| switch (err) { - error.ReadFailed => return fsr.file.err.?, + var fr = archive.file.reader(io, &buf); + elf.loadArchive(archive.path, &fr) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, else => |e| return e, }; }, } } -const FileSliceReader = struct { - file: std.Io.File.Reader, - file_location: MappedFile.Node.FileLocation, - reader: std.Io.Reader.Limited, - - pub fn init(file: std.Io.File.Reader) FileSliceReader { - return .{ .file = file, .file_location = undefined, .reader = undefined }; - } - - pub fn reset(fsr: *FileSliceReader, size: u64, buffer: []u8) void { - fsr.file_location = .{ - .offset = fsr.file.logicalPos(), - .size = size, - }; - fsr.reader = .init(&fsr.file.interface, .limited(@intCast(size)), buffer); - } - - pub fn pos(fsr: *const FileSliceReader) u64 { - return fsr.file.logicalPos() - fsr.file_location.offset; - } - - pub fn logicalPos(fsr: *const FileSliceReader) u64 { - return fsr.pos() - fsr.reader.interface.bufferedLen(); - } - - pub fn seekTo(fsr: *FileSliceReader, offset: u64) std.Io.File.Reader.SeekError!void { - if (offset > fsr.file_location.size) return error.EndOfStream; - const logical_pos = fsr.logicalPos(); - if (offset < logical_pos or offset >= fsr.pos()) { - fsr.reader.interface.tossBuffered(); - try fsr.file.seekTo(fsr.file_location.offset + offset); - fsr.reader.remaining = .limited(@intCast(fsr.file_location.size - offset)); - } else fsr.reader.interface.toss(@intCast(offset - logical_pos)); - } -}; -fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fsr: *FileSliceReader) !void { +fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; - const r = &fsr.file.interface; + const r = &fr.interface; log.debug("loadArchive({f})", .{path.fmtEscapeString()}); if (!std.mem.eql(u8, try r.take(std.elf.ARMAG.len), std.elf.ARMAG)) @@ -1333,6 +1300,7 @@ fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fsr: *FileSliceReader) !vo while (r.takeStruct(std.elf.ar_hdr, native_endian)) |header| { if (!std.mem.eql(u8, &header.ar_fmag, std.elf.ARFMAG)) return diags.failParse(path, "bad file magic", .{}); + const offset = fr.logicalPos(); const size = header.size() catch return diags.failParse(path, "bad member size", .{}); if (std.mem.eql(u8, &header.ar_name, std.elf.STRNAME)) { @@ -1361,15 +1329,11 @@ fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fsr: *FileSliceReader) !vo error.Overflow => return diags.failParse(path, "bad member name offset", .{}), }; if (!std.mem.endsWith(u8, member, ".o")) break :load_object; - var buf: [4096]u8 = undefined; - fsr.reset(size, &buf); - try elf.loadObject(path, member, fsr); - try fsr.seekTo(size); - continue; + try elf.loadObject(path, member, fr, .{ .offset = offset, .size = size }); } - try r.discardAll(size); + try fr.seekTo(std.mem.alignForward(u64, offset + size, 2)); } else |err| switch (err) { - error.EndOfStream => if (!fsr.file.atEnd()) return error.EndOfStream, + error.EndOfStream => if (!fr.atEnd()) return error.EndOfStream, else => |e| return e, } } @@ -1379,11 +1343,17 @@ fn fmtMemberString(member: ?[]const u8) std.fmt.Alt(?[]const u8, memberStringEsc fn memberStringEscape(member: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { try w.print("({f})", .{std.zig.fmtString(member orelse return)}); } -fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: *FileSliceReader) !void { +fn loadObject( + elf: *Elf, + path: std.Build.Cache.Path, + member: ?[]const u8, + fr: *std.Io.File.Reader, + fl: MappedFile.Node.FileLocation, +) !void { const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; - const r = &fsr.reader.interface; + const r = &fr.interface; const ii: Node.InputIndex = @enumFromInt(elf.inputs.items.len); log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) }); @@ -1411,11 +1381,13 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * if (ehdr.machine != elf.ehdrField(.machine)) return diags.failParse(path, "bad machine", .{}); if (ehdr.shoff == 0 or ehdr.shnum <= 1) return; + if (ehdr.shoff + ehdr.shentsize * ehdr.shnum > fl.size) + return diags.failParse(path, "bad section header offset/size", .{}); if (ehdr.shentsize < @sizeOf(ElfN.Shdr)) return diags.failParse(path, "unsupported shentsize", .{}); const sections = try gpa.alloc(struct { shdr: ElfN.Shdr, si: Symbol.Index }, ehdr.shnum); defer gpa.free(sections); - try fsr.seekTo(ehdr.shoff); + try fr.seekTo(fl.offset + ehdr.shoff); for (sections) |*section| { section.* = .{ .shdr = try r.peekStruct(ElfN.Shdr, target_endian), @@ -1424,7 +1396,7 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * try r.discardAll(ehdr.shentsize); switch (section.shdr.type) { std.elf.SHT_NULL, std.elf.SHT_NOBITS => {}, - else => if (section.shdr.offset + section.shdr.size > fsr.file_location.size) + else => if (section.shdr.offset + section.shdr.size > fl.size) return diags.failParse(path, "bad section offset/size", .{}), } } @@ -1436,7 +1408,7 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * return diags.failParse(path, "invalid shstrtab type", .{}); const shstrtab = try gpa.alloc(u8, @intCast(shdr.size)); errdefer gpa.free(shstrtab); - try fsr.seekTo(shdr.offset); + try fr.seekTo(fl.offset + shdr.offset); try r.readSliceAll(shstrtab); break :shstrtab shstrtab; }; @@ -1470,7 +1442,7 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * .ii = ii, .si = section.si, .file_location = .{ - .offset = fsr.file_location.offset + section.shdr.offset, + .offset = fl.offset + section.shdr.offset, .size = section.shdr.size, }, }; @@ -1492,7 +1464,7 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * return diags.failParse(path, "invalid strtab type", .{}); const strtab = try gpa.alloc(u8, @intCast(shdr.size)); errdefer gpa.free(strtab); - try fsr.seekTo(shdr.offset); + try fr.seekTo(fl.offset + shdr.offset); try r.readSliceAll(strtab); break :strtab strtab; }; @@ -1507,12 +1479,11 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * .{ symtab.shdr.size, symtab.shdr.entsize }, ); symmap.clearRetainingCapacity(); - try symmap.resize(gpa, symnum); + try symmap.resize(gpa, std.math.sub(u32, symnum, 1) catch continue); try elf.symtab.ensureUnusedCapacity(gpa, symnum); try elf.globals.ensureUnusedCapacity(gpa, symnum); - try fsr.seekTo(symtab.shdr.offset + symtab.shdr.entsize); - symmap.items[0] = .null; - for (symmap.items[1..]) |*si| { + try fr.seekTo(fl.offset + symtab.shdr.offset + symtab.shdr.entsize); + for (symmap.items) |*si| { si.* = .null; const input_sym = try r.peekStruct(ElfN.Sym, target_endian); try r.discardAll64(symtab.shdr.entsize); @@ -1594,12 +1565,12 @@ fn loadObject(elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, fsr: * .{ rels.shdr.size, rels.shdr.entsize }, ); try elf.relocs.ensureUnusedCapacity(gpa, relnum); - try fsr.seekTo(rels.shdr.offset); + try fr.seekTo(fl.offset + rels.shdr.offset); for (0..relnum) |_| { const rel = try r.peekStruct(Rel, target_endian); try r.discardAll64(rels.shdr.entsize); if (rel.info.sym >= symnum) continue; - const target_si = symmap.items[rel.info.sym]; + const target_si = symmap.items[rel.info.sym - 1]; if (target_si == .null) continue; elf.addRelocAssumeCapacity( loc_sec.si, From 0834e696f75d8477e5bc7a2dc49e7d10800039bc Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 29 Oct 2025 18:04:11 -0400 Subject: [PATCH 5/5] Elf2: start implementing dynamic linking --- lib/std/Build/Module.zig | 11 +- lib/std/Target/Query.zig | 17 +- lib/std/c.zig | 3 +- lib/std/start.zig | 2 +- lib/std/zig/system.zig | 11 +- src/Compilation/Config.zig | 31 +- src/codegen/x86_64/Emit.zig | 12 +- src/link/Elf.zig | 18 +- src/link/Elf2.zig | 707 +++++++++++++++++++++++++++--------- src/link/Lld.zig | 9 +- src/main.zig | 30 +- 11 files changed, 625 insertions(+), 226 deletions(-) diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 2719aea1c6..657f8bb74e 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -596,10 +596,13 @@ pub fn appendZigProcessFlags( "-target", try target.query.zigTriple(b.allocator), "-mcpu", try target.query.serializeCpuAlloc(b.allocator), }); - - if (target.query.dynamic_linker.get()) |dynamic_linker| { - try zig_args.append("--dynamic-linker"); - try zig_args.append(dynamic_linker); + if (target.query.dynamic_linker) |dynamic_linker| { + if (dynamic_linker.get()) |dynamic_linker_path| { + try zig_args.append("--dynamic-linker"); + try zig_args.append(dynamic_linker_path); + } else { + try zig_args.append("--no-dynamic-linker"); + } } } } diff --git a/lib/std/Target/Query.zig b/lib/std/Target/Query.zig index f3f5155b06..2f00f54268 100644 --- a/lib/std/Target/Query.zig +++ b/lib/std/Target/Query.zig @@ -46,8 +46,9 @@ android_api_level: ?u32 = null, abi: ?Target.Abi = null, /// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path -/// based on the `os_tag`. -dynamic_linker: Target.DynamicLinker = .none, +/// based on the `os_tag`. When `dynamic_linker` is a non-`null` empty string, no dynamic +/// linker is used regardless of `os_tag`. +dynamic_linker: ?Target.DynamicLinker = null, /// `null` means default for the cpu/arch/os combo. ofmt: ?Target.ObjectFormat = null, @@ -213,7 +214,7 @@ pub fn parse(args: ParseOptions) !Query { const diags = args.diagnostics orelse &dummy_diags; var result: Query = .{ - .dynamic_linker = Target.DynamicLinker.init(args.dynamic_linker), + .dynamic_linker = if (args.dynamic_linker) |dynamic_linker| .init(dynamic_linker) else null, }; var it = mem.splitScalar(u8, args.arch_os_abi, '-'); @@ -381,7 +382,7 @@ pub fn isNativeCpu(self: Query) bool { pub fn isNativeOs(self: Query) bool { return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and - self.dynamic_linker.get() == null and self.glibc_version == null and self.android_api_level == null; + self.dynamic_linker == null and self.glibc_version == null and self.android_api_level == null; } pub fn isNativeAbi(self: Query) bool { @@ -599,7 +600,7 @@ pub fn eql(a: Query, b: Query) bool { if (!versionEqualOpt(a.glibc_version, b.glibc_version)) return false; if (a.android_api_level != b.android_api_level) return false; if (a.abi != b.abi) return false; - if (!a.dynamic_linker.eql(b.dynamic_linker)) return false; + if (!dynamicLinkerEqualOpt(a.dynamic_linker, b.dynamic_linker)) return false; if (a.ofmt != b.ofmt) return false; return true; @@ -611,6 +612,12 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool { return SemanticVersion.order(a.?, b.?) == .eq; } +fn dynamicLinkerEqualOpt(a: ?Target.DynamicLinker, b: ?Target.DynamicLinker) bool { + if (a == null and b == null) return true; + if (a == null or b == null) return false; + return a.?.eql(b.?); +} + test parse { const io = std.testing.io; diff --git a/lib/std/c.zig b/lib/std/c.zig index 2f96404451..9d50cac2a0 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -7013,7 +7013,8 @@ pub const RTLD = switch (native_os) { LAZY: bool = false, NOW: bool = false, NOLOAD: bool = false, - _3: u5 = 0, + DEEPBIND: bool = false, + _4: u4 = 0, GLOBAL: bool = false, _9: u3 = 0, NODELETE: bool = false, diff --git a/lib/std/start.zig b/lib/std/start.zig index a563912bbc..7f7cc2f083 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -562,7 +562,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { // Apply the initial relocations as early as possible in the startup process. We cannot // make calls yet on some architectures (e.g. MIPS) *because* they haven't been applied yet, // so this must be fully inlined. - if (builtin.position_independent_executable) { + if (builtin.link_mode == .static and builtin.position_independent_executable) { @call(.always_inline, std.pie.relocate, .{phdrs}); } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index fc433d9936..c45f6db789 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -585,10 +585,10 @@ fn abiAndDynamicLinkerFromFile( .os = os, .abi = query.abi orelse Target.Abi.default(cpu.arch, os.tag), .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), - .dynamic_linker = query.dynamic_linker, + .dynamic_linker = query.dynamic_linker orelse .none, }; var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC - const look_for_ld = query.dynamic_linker.get() == null; + const look_for_ld = query.dynamic_linker == null; var got_dyn_section: bool = false; { @@ -938,7 +938,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ const is_linux = builtin.target.os.tag == .linux; const is_illumos = builtin.target.os.tag == .illumos; const is_darwin = builtin.target.os.tag.isDarwin(); - const have_all_info = query.dynamic_linker.get() != null and + const have_all_info = query.dynamic_linker != null and query.abi != null and (!is_linux or query.abi.?.isGnu()); const os_is_non_native = query.os_tag != null; // The illumos environment is always the same. @@ -1126,10 +1126,7 @@ fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Quer .os = os, .abi = abi, .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), - .dynamic_linker = if (query.dynamic_linker.get() == null) - Target.DynamicLinker.standard(cpu, os, abi) - else - query.dynamic_linker, + .dynamic_linker = query.dynamic_linker orelse .standard(cpu, os, abi), }; } diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index c2b9240480..2eca2c8411 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -123,6 +123,7 @@ pub const ResolveError = error{ WasiExecModelRequiresWasi, SharedMemoryIsWasmOnly, ObjectFilesCannotShareMemory, + ObjectFilesCannotSpecifyDynamicLinker, SharedMemoryRequiresAtomicsAndBulkMemory, ThreadsRequireSharedMemory, EmittingLlvmModuleRequiresLlvmBackend, @@ -131,6 +132,7 @@ pub const ResolveError = error{ EmittingBinaryRequiresLlvmLibrary, LldIncompatibleObjectFormat, LldCannotIncrementallyLink, + LldCannotSpecifyDynamicLinkerForSharedLibraries, LtoRequiresLld, SanitizeThreadRequiresLibCpp, LibCRequiresLibUnwind, @@ -142,6 +144,7 @@ pub const ResolveError = error{ TargetCannotStaticLinkExecutables, LibCRequiresDynamicLinking, SharedLibrariesRequireDynamicLinking, + DynamicLinkingWithLldRequiresSharedLibraries, ExportMemoryAndDynamicIncompatible, DynamicLibraryPrecludesPie, TargetRequiresPie, @@ -274,16 +277,11 @@ pub fn resolve(options: Options) ResolveError!Config { if (options.link_mode == .static) return error.LibCRequiresDynamicLinking; break :b .dynamic; } - // When creating a executable that links to system libraries, we - // require dynamic linking, but we must not link static libraries - // or object files dynamically! - if (options.any_dyn_libs and options.output_mode == .Exe) { - if (options.link_mode == .static) return error.SharedLibrariesRequireDynamicLinking; - break :b .dynamic; - } if (options.link_mode) |link_mode| break :b link_mode; + if (options.any_dyn_libs) break :b .dynamic; + if (explicitly_exe_or_dyn_lib and link_libc) { // When using the native glibc/musl ABI, dynamic linking is usually what people want. if (options.resolved_target.is_native_abi and (target.isGnuLibC() or target.isMuslLibC())) { @@ -425,6 +423,25 @@ pub fn resolve(options: Options) ResolveError!Config { break :b use_llvm; }; + switch (options.output_mode) { + .Exe => if (options.any_dyn_libs) { + // When creating a executable that links to system libraries, we + // require dynamic linking, but we must not link static libraries + // or object files dynamically! + if (link_mode == .static) return error.SharedLibrariesRequireDynamicLinking; + } else if (use_lld and !link_libc and !link_libcpp and !link_libunwind) { + // Lld does not support creating dynamic executables when not + // linking to any shared libraries. + if (link_mode == .dynamic) return error.DynamicLinkingWithLldRequiresSharedLibraries; + }, + .Lib => if (use_lld and options.resolved_target.is_explicit_dynamic_linker) { + return error.LldCannotSpecifyDynamicLinkerForSharedLibraries; + }, + .Obj => if (options.resolved_target.is_explicit_dynamic_linker) { + return error.ObjectFilesCannotSpecifyDynamicLinker; + }, + } + const use_new_linker = b: { if (use_lld) { if (options.use_new_linker == true) return error.NewLinkerIncompatibleWithLld; diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index 0aac146bc0..d859ba3c92 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -182,6 +182,10 @@ pub fn emitMir(emit: *Emit) Error!void { try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null) else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{ .name = extern_func.toSlice(&emit.lower.mir).?, + .lib_name = switch (comp.compiler_rt_strat) { + .none, .lib, .obj, .zcu => null, + .dyn_lib => "compiler_rt", + }, .type = .FUNC, })) else if (emit.bin_file.cast(.macho)) |macho_file| try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null) @@ -320,10 +324,12 @@ pub fn emitMir(emit: *Emit) Error!void { }, emit.lower.target), &.{.{ .op_index = 0, .target = .{ - .index = if (emit.bin_file.cast(.elf)) |elf_file| - try elf_file.getGlobalSymbol("__tls_get_addr", null) - else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{ + .index = if (emit.bin_file.cast(.elf)) |elf_file| try elf_file.getGlobalSymbol( + "__tls_get_addr", + if (comp.config.link_libc) "c" else null, + ) else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{ .name = "__tls_get_addr", + .lib_name = if (comp.config.link_libc) "c" else null, .type = .FUNC, })) else unreachable, .is_extern = true, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2537332c28..1a7cb4c14a 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1882,17 +1882,13 @@ fn initSyntheticSections(self: *Elf) !void { const comp = self.base.comp; const target = self.getTarget(); const ptr_size = self.ptrWidthBytes(); - const shared_objects = self.shared_objects.values(); const is_exe_or_dyn_lib = switch (comp.config.output_mode) { .Exe => true, .Lib => comp.config.link_mode == .dynamic, .Obj => false, }; - const have_dynamic_linker = comp.config.link_mode == .dynamic and is_exe_or_dyn_lib and !target.dynamic_linker.eql(.none); - - const needs_interp = have_dynamic_linker and - (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker); + const have_dynamic_linker = comp.config.link_mode == .dynamic and is_exe_or_dyn_lib; const needs_eh_frame = blk: { if (self.zigObjectPtr()) |zo| @@ -2004,7 +2000,15 @@ fn initSyntheticSections(self: *Elf) !void { }); } - if (needs_interp and self.section_indexes.interp == null) { + if (needs_interp: { + if (comp.config.link_mode == .static) break :needs_interp false; + if (target.dynamic_linker.get() == null) break :needs_interp false; + break :needs_interp switch (comp.config.output_mode) { + .Exe => true, + .Lib => comp.root_mod.resolved_target.is_explicit_dynamic_linker, + .Obj => false, + }; + } and self.section_indexes.interp == null) { self.section_indexes.interp = try self.addSection(.{ .name = try self.insertShString(".interp"), .type = elf.SHT_PROGBITS, @@ -2013,7 +2017,7 @@ fn initSyntheticSections(self: *Elf) !void { }); } - if (self.isEffectivelyDynLib() or shared_objects.len > 0 or comp.config.pie) { + if (have_dynamic_linker or comp.config.pie or self.isEffectivelyDynLib()) { if (self.section_indexes.dynstrtab == null) { self.section_indexes.dynstrtab = try self.addSection(.{ .name = try self.insertShString(".dynstr"), diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index c985c32fc9..e46cbff5a2 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -1,11 +1,16 @@ base: link.File, +options: link.File.OpenOptions, mf: MappedFile, -known: Node.Known, +ni: Node.Known, nodes: std.MultiArrayList(Node), phdrs: std.ArrayList(MappedFile.Node.Index), +si: Symbol.Known, symtab: std.ArrayList(Symbol), shstrtab: StringTable, strtab: StringTable, +dynsym: std.ArrayList(Symbol.Index), +dynstr: StringTable, +needed: std.AutoArrayHashMapUnmanaged(u32, void), inputs: std.ArrayList(struct { path: std.Build.Cache.Path, member: ?[]const u8, @@ -143,13 +148,13 @@ pub const Node = union(enum) { }; pub const Known = struct { - pub const rodata: MappedFile.Node.Index = @enumFromInt(1); - pub const ehdr: MappedFile.Node.Index = @enumFromInt(2); - pub const phdr: MappedFile.Node.Index = @enumFromInt(3); - pub const shdr: MappedFile.Node.Index = @enumFromInt(4); - pub const text: MappedFile.Node.Index = @enumFromInt(5); - pub const data: MappedFile.Node.Index = @enumFromInt(6); - + comptime file: MappedFile.Node.Index = .root, + comptime ehdr: MappedFile.Node.Index = @enumFromInt(1), + comptime shdr: MappedFile.Node.Index = @enumFromInt(2), + comptime rodata: MappedFile.Node.Index = @enumFromInt(3), + comptime phdr: MappedFile.Node.Index = @enumFromInt(4), + comptime text: MappedFile.Node.Index = @enumFromInt(5), + comptime data: MappedFile.Node.Index = @enumFromInt(6), tls: MappedFile.Node.Index, }; @@ -231,7 +236,6 @@ pub const Symbol = struct { rodata, text, data, - tdata, _, pub fn get(si: Symbol.Index, elf: *Elf) *Symbol { @@ -250,6 +254,7 @@ pub const Symbol = struct { pub const InitOptions = struct { name: []const u8 = "", + lib_name: ?[]const u8 = null, value: u64 = 0, size: u64 = 0, type: std.elf.STT, @@ -258,29 +263,45 @@ pub const Symbol = struct { shndx: std.elf.Section = std.elf.SHN_UNDEF, }; pub fn init(si: Symbol.Index, elf: *Elf, opts: InitOptions) !void { - const name_entry = try elf.string(.strtab, opts.name); - try Symbol.Index.symtab.node(elf).resize( - &elf.mf, - elf.base.comp.gpa, - @as(usize, switch (elf.identClass()) { - .NONE, _ => unreachable, - .@"32" => @sizeOf(std.elf.Elf32.Sym), - .@"64" => @sizeOf(std.elf.Elf64.Sym), - }) * elf.symtab.items.len, - ); + const gpa = elf.base.comp.gpa; + const target_endian = elf.targetEndian(); + const sym_size: usize = switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| @sizeOf(class.ElfN().Sym), + }; + const name_strtab_entry = try elf.string(.strtab, opts.name); + try elf.si.symtab.node(elf).resize(&elf.mf, gpa, sym_size * elf.symtab.items.len); switch (elf.symPtr(si)) { - inline else => |sym| sym.* = .{ - .name = name_entry, - .value = @intCast(opts.value), - .size = @intCast(opts.size), - .info = .{ - .type = opts.type, - .bind = opts.bind, - }, - .other = .{ - .visibility = opts.visibility, - }, - .shndx = opts.shndx, + inline else => |sym, class| { + sym.* = .{ + .name = name_strtab_entry, + .value = @intCast(opts.value), + .size = @intCast(opts.size), + .info = .{ .type = opts.type, .bind = opts.bind }, + .other = .{ .visibility = opts.visibility }, + .shndx = opts.shndx, + }; + if (target_endian != native_endian) std.mem.byteSwapAllFields(class.ElfN().Sym, sym); + }, + } + if (opts.bind == .LOCAL or elf.si.dynsym == .null) return; + const dsi = elf.dynsym.items.len; + try elf.dynsym.append(gpa, si); + const dynsym_ni = elf.si.dynsym.node(elf); + const name_dynstr_entry = try elf.string(.dynstr, opts.name); + try dynsym_ni.resize(&elf.mf, gpa, sym_size * elf.dynsym.items.len); + switch (elf.dynsymSlice()) { + inline else => |dynsym, class| { + const dsym = &dynsym[dsi]; + dsym.* = .{ + .name = name_dynstr_entry, + .value = @intCast(opts.value), + .size = @intCast(opts.size), + .info = .{ .type = opts.type, .bind = opts.bind }, + .other = .{ .visibility = opts.visibility }, + .shndx = opts.shndx, + }; + if (target_endian != native_endian) std.mem.byteSwapAllFields(class.ElfN().Sym, dsym); }, } } @@ -329,6 +350,19 @@ pub const Symbol = struct { } }; + pub const Known = struct { + comptime symtab: Symbol.Index = .symtab, + comptime shstrtab: Symbol.Index = .shstrtab, + comptime strtab: Symbol.Index = .strtab, + comptime rodata: Symbol.Index = .rodata, + comptime text: Symbol.Index = .text, + comptime data: Symbol.Index = .data, + dynsym: Symbol.Index, + dynstr: Symbol.Index, + dynamic: Symbol.Index, + tdata: Symbol.Index, + }; + comptime { if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 16); } @@ -349,6 +383,22 @@ pub const Reloc = extern struct { AARCH64: std.elf.R_AARCH64, RISCV: std.elf.R_RISCV, PPC64: std.elf.R_PPC64, + + pub fn absAddr(elf: *Elf) Reloc.Type { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .AARCH64 => .{ .AARCH64 = .ABS64 }, + .PPC64 => .{ .PPC64 = .ADDR64 }, + .RISCV => .{ .RISCV = .@"64" }, + .X86_64 => .{ .X86_64 = .@"64" }, + }; + } + pub fn sizeAddr(elf: *Elf) Reloc.Type { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .X86_64 => .{ .X86_64 = .SIZE64 }, + }; + } }; pub const Index = enum(u32) { @@ -411,7 +461,7 @@ pub const Reloc = extern struct { ), .TPOFF32 => { const phdr = @field(elf.phdrSlice(), @tagName(class)); - const ph = &phdr[elf.getNode(elf.known.tls).segment]; + const ph = &phdr[elf.getNode(elf.ni.tls).segment]; assert(elf.targetLoad(&ph.type) == std.elf.PT_TLS); std.mem.writeInt( i32, @@ -420,6 +470,18 @@ pub const Reloc = extern struct { target_endian, ); }, + .SIZE32 => std.mem.writeInt( + u32, + loc_slice[0..4], + @intCast(elf.targetLoad(&target_sym.size)), + target_endian, + ), + .SIZE64 => std.mem.writeInt( + u64, + loc_slice[0..8], + @intCast(elf.targetLoad(&target_sym.size)), + target_endian, + ), }, } }, @@ -469,7 +531,6 @@ fn create( path: std.Build.Cache.Path, options: link.File.OpenOptions, ) !*Elf { - _ = options; const target = &comp.root_mod.resolved_target.result; assert(target.ofmt == .elf); const class: std.elf.CLASS = switch (target.ptrBitWidth()) { @@ -502,12 +563,16 @@ fn create( .Obj => .REL, }; const machine = target.toElfMachine(); - const maybe_interp = switch (comp.config.output_mode) { - .Exe, .Lib => switch (comp.config.link_mode) { - .static => null, - .dynamic => target.dynamic_linker.get(), + const maybe_interp = switch (comp.config.link_mode) { + .static => null, + .dynamic => switch (comp.config.output_mode) { + .Exe => target.dynamic_linker.get(), + .Lib => if (comp.root_mod.resolved_target.is_explicit_dynamic_linker) + target.dynamic_linker.get() + else + null, + .Obj => null, }, - .Obj => null, }; const elf = try arena.create(Elf); @@ -530,12 +595,19 @@ fn create( .allow_shlib_undefined = false, .stack_size = 0, }, + .options = options, .mf = try .init(file, comp.gpa), - .known = .{ + .ni = .{ .tls = .none, }, .nodes = .empty, .phdrs = .empty, + .si = .{ + .dynsym = .null, + .dynstr = .null, + .dynamic = .null, + .tdata = .null, + }, .symtab = .empty, .shstrtab = .{ .map = .empty, @@ -545,6 +617,12 @@ fn create( .map = .empty, .size = 1, }, + .dynsym = .empty, + .dynstr = .{ + .map = .empty, + .size = 1, + }, + .needed = .empty, .inputs = .empty, .input_sections = .empty, .input_section_pending_index = 0, @@ -576,6 +654,9 @@ pub fn deinit(elf: *Elf) void { elf.symtab.deinit(gpa); elf.shstrtab.map.deinit(gpa); elf.strtab.map.deinit(gpa); + elf.dynsym.deinit(gpa); + elf.dynstr.map.deinit(gpa); + elf.needed.deinit(gpa); for (elf.inputs.items) |input| if (input.member) |m| gpa.free(m); elf.inputs.deinit(gpa); elf.input_sections.deinit(gpa); @@ -599,6 +680,13 @@ fn initHeaders( ) !void { const comp = elf.base.comp; const gpa = comp.gpa; + const have_dynamic_section = switch (@"type") { + .NONE => unreachable, + .REL => false, + .EXEC => comp.config.link_mode == .dynamic, + .DYN => true, + .CORE, _ => unreachable, + }; const addr_align: std.mem.Alignment = switch (class) { .NONE, _ => unreachable, .@"32" => .@"4", @@ -618,37 +706,32 @@ fn initHeaders( phnum += 1; const data_phndx = phnum; phnum += 1; + const dynamic_phndx = if (have_dynamic_section) phndx: { + defer phnum += 1; + break :phndx phnum; + } else undefined; const tls_phndx = if (comp.config.any_non_single_threaded) phndx: { defer phnum += 1; break :phndx phnum; } else undefined; - const expected_nodes_len = 5 + phnum * 2; + const expected_nodes_len = 5 + phnum * 2 + @as(usize, 2) * @intFromBool(have_dynamic_section); try elf.nodes.ensureTotalCapacity(gpa, expected_nodes_len); try elf.phdrs.resize(gpa, phnum); elf.nodes.appendAssumeCapacity(.file); - assert(Node.Known.rodata == try elf.mf.addOnlyChildNode(gpa, .root, .{ - .alignment = elf.mf.flags.block_size, - .fixed = true, - .moved = true, - .bubbles_moved = false, - })); - elf.nodes.appendAssumeCapacity(.{ .segment = rodata_phndx }); - elf.phdrs.items[rodata_phndx] = Node.Known.rodata; - switch (class) { .NONE, _ => unreachable, inline else => |ct_class| { const ElfN = ct_class.ElfN(); - assert(Node.Known.ehdr == try elf.mf.addOnlyChildNode(gpa, Node.Known.rodata, .{ + assert(elf.ni.ehdr == try elf.mf.addOnlyChildNode(gpa, elf.ni.file, .{ .size = @sizeOf(ElfN.Ehdr), .alignment = addr_align, .fixed = true, })); elf.nodes.appendAssumeCapacity(.ehdr); - const ehdr: *ElfN.Ehdr = @ptrCast(@alignCast(Node.Known.ehdr.slice(&elf.mf))); + const ehdr: *ElfN.Ehdr = @ptrCast(@alignCast(elf.ni.ehdr.slice(&elf.mf))); const EI = std.elf.EI; @memcpy(ehdr.ident[0..std.elf.MAGIC.len], std.elf.MAGIC); ehdr.ident[EI.CLASS] = @intFromEnum(class); @@ -674,7 +757,23 @@ fn initHeaders( }, } - assert(Node.Known.phdr == try elf.mf.addLastChildNode(gpa, Node.Known.rodata, .{ + assert(elf.ni.shdr == try elf.mf.addLastChildNode(gpa, elf.ni.file, .{ + .size = elf.ehdrField(.shentsize) * elf.ehdrField(.shnum), + .alignment = addr_align, + .moved = true, + .resized = true, + })); + elf.nodes.appendAssumeCapacity(.shdr); + + assert(elf.ni.rodata == try elf.mf.addLastChildNode(gpa, elf.ni.file, .{ + .alignment = elf.mf.flags.block_size, + .moved = true, + .bubbles_moved = false, + })); + elf.nodes.appendAssumeCapacity(.{ .segment = rodata_phndx }); + elf.phdrs.items[rodata_phndx] = elf.ni.rodata; + + assert(elf.ni.phdr == try elf.mf.addOnlyChildNode(gpa, elf.ni.rodata, .{ .size = elf.ehdrField(.phentsize) * elf.ehdrField(.phnum), .alignment = addr_align, .moved = true, @@ -682,29 +781,23 @@ fn initHeaders( .bubbles_moved = false, })); elf.nodes.appendAssumeCapacity(.{ .segment = phdr_phndx }); - elf.phdrs.items[phdr_phndx] = Node.Known.phdr; + elf.phdrs.items[phdr_phndx] = elf.ni.phdr; - assert(Node.Known.shdr == try elf.mf.addLastChildNode(gpa, Node.Known.rodata, .{ - .size = elf.ehdrField(.shentsize) * elf.ehdrField(.shnum), - .alignment = addr_align, - })); - elf.nodes.appendAssumeCapacity(.shdr); - - assert(Node.Known.text == try elf.mf.addLastChildNode(gpa, .root, .{ + assert(elf.ni.text == try elf.mf.addLastChildNode(gpa, elf.ni.file, .{ .alignment = elf.mf.flags.block_size, .moved = true, .bubbles_moved = false, })); elf.nodes.appendAssumeCapacity(.{ .segment = text_phndx }); - elf.phdrs.items[text_phndx] = Node.Known.text; + elf.phdrs.items[text_phndx] = elf.ni.text; - assert(Node.Known.data == try elf.mf.addLastChildNode(gpa, .root, .{ + assert(elf.ni.data == try elf.mf.addLastChildNode(gpa, elf.ni.file, .{ .alignment = elf.mf.flags.block_size, .moved = true, .bubbles_moved = false, })); elf.nodes.appendAssumeCapacity(.{ .segment = data_phndx }); - elf.phdrs.items[data_phndx] = Node.Known.data; + elf.phdrs.items[data_phndx] = elf.ni.data; var ph_vaddr: u32 = switch (elf.ehdrField(.type)) { else => 0, @@ -723,7 +816,7 @@ fn initHeaders( const ElfN = ct_class.ElfN(); const target_endian = elf.targetEndian(); - const phdr: []ElfN.Phdr = @ptrCast(@alignCast(Node.Known.phdr.slice(&elf.mf))); + const phdr: []ElfN.Phdr = @ptrCast(@alignCast(elf.ni.phdr.slice(&elf.mf))); const ph_phdr = &phdr[phdr_phndx]; ph_phdr.* = .{ .type = std.elf.PT_PHDR, @@ -733,7 +826,7 @@ fn initHeaders( .filesz = 0, .memsz = 0, .flags = .{ .R = true }, - .@"align" = @intCast(Node.Known.phdr.alignment(&elf.mf).toByteUnits()), + .@"align" = @intCast(elf.ni.phdr.alignment(&elf.mf).toByteUnits()), }; if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_phdr); @@ -752,7 +845,7 @@ fn initHeaders( if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_interp); } - _, const rodata_size = Node.Known.rodata.location(&elf.mf).resolve(&elf.mf); + _, const rodata_size = elf.ni.rodata.location(&elf.mf).resolve(&elf.mf); const ph_rodata = &phdr[rodata_phndx]; ph_rodata.* = .{ .type = std.elf.PT_NULL, @@ -762,12 +855,12 @@ fn initHeaders( .filesz = @intCast(rodata_size), .memsz = @intCast(rodata_size), .flags = .{ .R = true }, - .@"align" = @intCast(Node.Known.rodata.alignment(&elf.mf).toByteUnits()), + .@"align" = @intCast(elf.ni.rodata.alignment(&elf.mf).toByteUnits()), }; if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_rodata); ph_vaddr += @intCast(rodata_size); - _, const text_size = Node.Known.text.location(&elf.mf).resolve(&elf.mf); + _, const text_size = elf.ni.text.location(&elf.mf).resolve(&elf.mf); const ph_text = &phdr[text_phndx]; ph_text.* = .{ .type = std.elf.PT_NULL, @@ -777,12 +870,12 @@ fn initHeaders( .filesz = @intCast(text_size), .memsz = @intCast(text_size), .flags = .{ .R = true, .X = true }, - .@"align" = @intCast(Node.Known.text.alignment(&elf.mf).toByteUnits()), + .@"align" = @intCast(elf.ni.text.alignment(&elf.mf).toByteUnits()), }; if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_text); ph_vaddr += @intCast(text_size); - _, const data_size = Node.Known.data.location(&elf.mf).resolve(&elf.mf); + _, const data_size = elf.ni.data.location(&elf.mf).resolve(&elf.mf); const ph_data = &phdr[data_phndx]; ph_data.* = .{ .type = std.elf.PT_NULL, @@ -792,11 +885,26 @@ fn initHeaders( .filesz = @intCast(data_size), .memsz = @intCast(data_size), .flags = .{ .R = true, .W = true }, - .@"align" = @intCast(Node.Known.data.alignment(&elf.mf).toByteUnits()), + .@"align" = @intCast(elf.ni.data.alignment(&elf.mf).toByteUnits()), }; if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_data); ph_vaddr += @intCast(data_size); + if (have_dynamic_section) { + const ph_dynamic = &phdr[dynamic_phndx]; + ph_dynamic.* = .{ + .type = std.elf.PT_DYNAMIC, + .offset = 0, + .vaddr = 0, + .paddr = 0, + .filesz = 0, + .memsz = 0, + .flags = .{ .R = true, .W = true }, + .@"align" = @intCast(addr_align.toByteUnits()), + }; + if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_dynamic); + } + if (comp.config.any_non_single_threaded) { const ph_tls = &phdr[tls_phndx]; ph_tls.* = .{ @@ -812,7 +920,7 @@ fn initHeaders( if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_tls); } - const sh_null: *ElfN.Shdr = @ptrCast(@alignCast(Node.Known.shdr.slice(&elf.mf))); + const sh_null: *ElfN.Shdr = @ptrCast(@alignCast(elf.ni.shdr.slice(&elf.mf))); sh_null.* = .{ .name = try elf.string(.shstrtab, ""), .type = std.elf.SHT_NULL, @@ -834,99 +942,142 @@ fn initHeaders( .target_relocs = .none, .unused = 0, }; - assert(try elf.addSection(Node.Known.rodata, .{ + assert(elf.si.symtab == try elf.addSection(elf.ni.file, .{ .type = std.elf.SHT_SYMTAB, + .size = @sizeOf(ElfN.Sym) * 1, .addralign = addr_align, .entsize = @sizeOf(ElfN.Sym), - }) == .symtab); - - const symtab: *ElfN.Sym = @ptrCast(@alignCast(Symbol.Index.symtab.node(elf).slice(&elf.mf))); - symtab.* = .{ + })); + const symtab_null = @field(elf.symPtr(.null), @tagName(ct_class)); + symtab_null.* = .{ .name = try elf.string(.strtab, ""), .value = 0, .size = 0, - .info = .{ - .type = .NOTYPE, - .bind = .LOCAL, - }, - .other = .{ - .visibility = .DEFAULT, - }, + .info = .{ .type = .NOTYPE, .bind = .LOCAL }, + .other = .{ .visibility = .DEFAULT }, .shndx = std.elf.SHN_UNDEF, }; + if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Sym, symtab_null); const ehdr = @field(elf.ehdrPtr(), @tagName(ct_class)); ehdr.shstrndx = ehdr.shnum; }, } - assert(try elf.addSection(Node.Known.rodata, .{ + assert(elf.si.shstrtab == try elf.addSection(elf.ni.file, .{ .type = std.elf.SHT_STRTAB, .addralign = elf.mf.flags.block_size, .entsize = 1, - }) == .shstrtab); + })); try elf.renameSection(.symtab, ".symtab"); try elf.renameSection(.shstrtab, ".shstrtab"); - Symbol.Index.shstrtab.node(elf).slice(&elf.mf)[0] = 0; + elf.si.shstrtab.node(elf).slice(&elf.mf)[0] = 0; - assert(try elf.addSection(Node.Known.rodata, .{ + assert(elf.si.strtab == try elf.addSection(elf.ni.file, .{ .name = ".strtab", .type = std.elf.SHT_STRTAB, - .addralign = elf.mf.flags.block_size, .size = 1, + .addralign = elf.mf.flags.block_size, .entsize = 1, - }) == .strtab); + })); try elf.linkSections(.symtab, .strtab); - Symbol.Index.strtab.node(elf).slice(&elf.mf)[0] = 0; + elf.si.strtab.node(elf).slice(&elf.mf)[0] = 0; - assert(try elf.addSection(Node.Known.rodata, .{ + assert(elf.si.rodata == try elf.addSection(elf.ni.rodata, .{ .name = ".rodata", .flags = .{ .ALLOC = true }, .addralign = elf.mf.flags.block_size, - }) == .rodata); - assert(try elf.addSection(Node.Known.text, .{ + })); + assert(elf.si.text == try elf.addSection(elf.ni.text, .{ .name = ".text", .flags = .{ .ALLOC = true, .EXECINSTR = true }, .addralign = elf.mf.flags.block_size, - }) == .text); - assert(try elf.addSection(Node.Known.data, .{ + })); + assert(elf.si.data == try elf.addSection(elf.ni.data, .{ .name = ".data", .flags = .{ .WRITE = true, .ALLOC = true }, .addralign = elf.mf.flags.block_size, - }) == .data); - if (comp.config.any_non_single_threaded) { - try elf.nodes.ensureUnusedCapacity(gpa, 1); - elf.known.tls = try elf.mf.addLastChildNode(gpa, Node.Known.rodata, .{ - .alignment = elf.mf.flags.block_size, - .moved = true, - }); - elf.nodes.appendAssumeCapacity(.{ .segment = tls_phndx }); - elf.phdrs.items[tls_phndx] = elf.known.tls; - - assert(try elf.addSection(elf.known.tls, .{ - .name = ".tdata", - .flags = .{ .WRITE = true, .ALLOC = true, .TLS = true }, - .addralign = elf.mf.flags.block_size, - }) == .tdata); - } + })); if (maybe_interp) |interp| { - try elf.nodes.ensureUnusedCapacity(gpa, 1); - const interp_ni = try elf.mf.addLastChildNode(gpa, Node.Known.rodata, .{ + const interp_ni = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{ .size = interp.len + 1, .moved = true, .resized = true, + .bubbles_moved = false, }); elf.nodes.appendAssumeCapacity(.{ .segment = interp_phndx }); elf.phdrs.items[interp_phndx] = interp_ni; const sec_interp_si = try elf.addSection(interp_ni, .{ .name = ".interp", - .size = @intCast(interp.len + 1), .flags = .{ .ALLOC = true }, + .size = @intCast(interp.len + 1), }); const sec_interp = sec_interp_si.node(elf).slice(&elf.mf); @memcpy(sec_interp[0..interp.len], interp); sec_interp[interp.len] = 0; } + if (have_dynamic_section) { + const dynamic_ni = try elf.mf.addLastChildNode(gpa, elf.ni.data, .{ + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = dynamic_phndx }); + elf.phdrs.items[dynamic_phndx] = dynamic_ni; + + switch (class) { + .NONE, _ => unreachable, + inline else => |ct_class| { + const ElfN = ct_class.ElfN(); + elf.si.dynsym = try elf.addSection(elf.ni.rodata, .{ + .name = ".dynsym", + .type = std.elf.SHT_DYNSYM, + .size = @sizeOf(ElfN.Sym) * 1, + .addralign = addr_align, + .entsize = @sizeOf(ElfN.Sym), + }); + const dynsym_null = &@field(elf.dynsymSlice(), @tagName(ct_class))[0]; + dynsym_null.* = .{ + .name = try elf.string(.dynstr, ""), + .value = 0, + .size = 0, + .info = .{ .type = .NOTYPE, .bind = .LOCAL }, + .other = .{ .visibility = .DEFAULT }, + .shndx = std.elf.SHN_UNDEF, + }; + if (elf.targetEndian() != native_endian) std.mem.byteSwapAllFields(ElfN.Sym, dynsym_null); + }, + } + elf.si.dynstr = try elf.addSection(elf.ni.rodata, .{ + .name = ".dynstr", + .type = std.elf.SHT_STRTAB, + .size = 1, + .addralign = elf.mf.flags.block_size, + .entsize = 1, + }); + elf.si.dynamic = try elf.addSection(dynamic_ni, .{ + .name = ".dynamic", + .type = std.elf.SHT_DYNAMIC, + .flags = .{ .ALLOC = true, .WRITE = true }, + .addralign = addr_align, + }); + try elf.linkSections(elf.si.dynamic, elf.si.dynstr); + try elf.linkSections(elf.si.dynsym, elf.si.dynstr); + } + if (comp.config.any_non_single_threaded) { + elf.ni.tls = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{ + .alignment = elf.mf.flags.block_size, + .moved = true, + .bubbles_moved = false, + }); + elf.nodes.appendAssumeCapacity(.{ .segment = tls_phndx }); + elf.phdrs.items[tls_phndx] = elf.ni.tls; + + elf.si.tdata = try elf.addSection(elf.ni.tls, .{ + .name = ".tdata", + .flags = .{ .WRITE = true, .ALLOC = true, .TLS = true }, + .addralign = elf.mf.flags.block_size, + }); + } assert(elf.nodes.len == expected_nodes_len); } @@ -962,7 +1113,8 @@ fn getNode(elf: *const Elf, ni: MappedFile.Node.Index) Node { fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { const parent_vaddr = parent_vaddr: { const parent_si = switch (elf.getNode(ni.parent(&elf.mf))) { - .file, .ehdr, .shdr => unreachable, + .file => return 0, + .ehdr, .shdr => unreachable, .segment => |phndx| break :parent_vaddr switch (elf.phdrSlice()) { inline else => |ph| elf.targetLoad(&ph[phndx].vaddr), }, @@ -970,7 +1122,7 @@ fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { .input_section => unreachable, inline .nav, .uav, .lazy_code, .lazy_const_data => |mi| mi.symbol(elf), }; - break :parent_vaddr switch (elf.symPtr(parent_si)) { + break :parent_vaddr if (parent_si == elf.si.tdata) 0 else switch (elf.symPtr(parent_si)) { inline else => |sym| elf.targetLoad(&sym.value), }; }; @@ -1025,7 +1177,7 @@ pub const EhdrPtr = union(std.elf.CLASS) { @"64": *std.elf.Elf64.Ehdr, }; pub fn ehdrPtr(elf: *Elf) EhdrPtr { - const slice = Node.Known.ehdr.slice(&elf.mf); + const slice = elf.ni.ehdr.slice(&elf.mf); return switch (elf.identClass()) { .NONE, _ => unreachable, inline else => |class| @unionInit( @@ -1050,7 +1202,7 @@ pub const PhdrSlice = union(std.elf.CLASS) { @"64": []std.elf.Elf64.Phdr, }; pub fn phdrSlice(elf: *Elf) PhdrSlice { - const slice = Node.Known.phdr.slice(&elf.mf); + const slice = elf.ni.phdr.slice(&elf.mf); return switch (elf.identClass()) { .NONE, _ => unreachable, inline else => |class| @unionInit( @@ -1067,7 +1219,7 @@ pub const ShdrSlice = union(std.elf.CLASS) { @"64": []std.elf.Elf64.Shdr, }; pub fn shdrSlice(elf: *Elf) ShdrSlice { - const slice = Node.Known.shdr.slice(&elf.mf); + const slice = elf.ni.shdr.slice(&elf.mf); return switch (elf.identClass()) { .NONE, _ => unreachable, inline else => |class| @unionInit( @@ -1084,7 +1236,7 @@ pub const SymtabSlice = union(std.elf.CLASS) { @"64": []std.elf.Elf64.Sym, }; pub fn symtabSlice(elf: *Elf) SymtabSlice { - const slice = Symbol.Index.symtab.node(elf).slice(&elf.mf); + const slice = elf.si.symtab.node(elf).slice(&elf.mf); return switch (elf.identClass()) { .NONE, _ => unreachable, inline else => |class| @unionInit( @@ -1106,6 +1258,18 @@ pub fn symPtr(elf: *Elf, si: Symbol.Index) SymPtr { }; } +pub fn dynsymSlice(elf: *Elf) SymtabSlice { + const slice = elf.si.dynsym.node(elf).slice(&elf.mf); + return switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| @unionInit( + SymtabSlice, + @tagName(class), + @ptrCast(@alignCast(slice)), + ), + }; +} + fn addSymbolAssumeCapacity(elf: *Elf) Symbol.Index { defer elf.symtab.addOneAssumeCapacity().* = .{ .ni = .none, @@ -1124,6 +1288,7 @@ fn initSymbolAssumeCapacity(elf: *Elf, opts: Symbol.Index.InitOptions) !Symbol.I pub fn globalSymbol(elf: *Elf, opts: struct { name: []const u8, + lib_name: ?[]const u8 = null, type: std.elf.STT, bind: std.elf.STB = .GLOBAL, visibility: std.elf.STV = .DEFAULT, @@ -1133,6 +1298,7 @@ pub fn globalSymbol(elf: *Elf, opts: struct { const global_gop = try elf.globals.getOrPut(gpa, try elf.string(.strtab, opts.name)); if (!global_gop.found_existing) global_gop.value_ptr.* = try elf.initSymbolAssumeCapacity(.{ .name = opts.name, + .lib_name = opts.lib_name, .type = opts.type, .bind = opts.bind, .visibility = opts.visibility, @@ -1169,15 +1335,15 @@ fn navType( }, }; } -fn namedSection(name: []const u8) ?Symbol.Index { +fn namedSection(elf: *const Elf, name: []const u8) ?Symbol.Index { if (std.mem.eql(u8, name, ".rodata") or - std.mem.startsWith(u8, name, ".rodata.")) return .rodata; + std.mem.startsWith(u8, name, ".rodata.")) return elf.si.rodata; if (std.mem.eql(u8, name, ".text") or - std.mem.startsWith(u8, name, ".text.")) return .text; + std.mem.startsWith(u8, name, ".text.")) return elf.si.text; if (std.mem.eql(u8, name, ".data") or - std.mem.startsWith(u8, name, ".data.")) return .data; + std.mem.startsWith(u8, name, ".data.")) return elf.si.data; if (std.mem.eql(u8, name, ".tdata") or - std.mem.startsWith(u8, name, ".tdata.")) return .tdata; + std.mem.startsWith(u8, name, ".tdata.")) return elf.si.tdata; return null; } fn navSection( @@ -1186,16 +1352,16 @@ fn navSection( nav_fr: @FieldType(@FieldType(InternPool.Nav, "status"), "fully_resolved"), ) Symbol.Index { if (nav_fr.@"linksection".toSlice(ip)) |@"linksection"| - if (namedSection(@"linksection")) |si| return si; + if (elf.namedSection(@"linksection")) |si| return si; return switch (navType( ip, .{ .fully_resolved = nav_fr }, elf.base.comp.config.any_non_single_threaded, )) { else => unreachable, - .FUNC => .text, - .OBJECT => .data, - .TLS => .tdata, + .FUNC => elf.si.text, + .OBJECT => elf.si.data, + .TLS => elf.si.tdata, }; } fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavMapIndex { @@ -1215,6 +1381,7 @@ pub fn navSymbol(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol. const nav = ip.getNav(nav_index); if (nav.getExtern(ip)) |@"extern"| return elf.globalSymbol(.{ .name = @"extern".name.toSlice(ip), + .lib_name = @"extern".lib_name.toSlice(ip), .type = navType(ip, nav.status, elf.base.comp.config.any_non_single_threaded), .bind = switch (@"extern".linkage) { .internal => .LOCAL, @@ -1266,7 +1433,6 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || const io = elf.base.comp.io; var buf: [4096]u8 = undefined; switch (input) { - else => {}, .object => |object| { var fr = object.file.reader(io, &buf); elf.loadObject(object.path, null, &fr, .{ @@ -1284,6 +1450,16 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || else => |e| return e, }; }, + .res => unreachable, + .dso => |dso| { + try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1); + var fr = dso.file.reader(io, &buf); + elf.loadDso(dso.path, &fr) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, + else => |e| return e, + }; + }, + .dso_exact => |dso_exact| try elf.loadDsoExact(dso_exact.name), } } fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { @@ -1382,7 +1558,7 @@ fn loadObject( return diags.failParse(path, "bad machine", .{}); if (ehdr.shoff == 0 or ehdr.shnum <= 1) return; if (ehdr.shoff + ehdr.shentsize * ehdr.shnum > fl.size) - return diags.failParse(path, "bad section header offset/size", .{}); + return diags.failParse(path, "bad section header location", .{}); if (ehdr.shentsize < @sizeOf(ElfN.Shdr)) return diags.failParse(path, "unsupported shentsize", .{}); const sections = try gpa.alloc(struct { shdr: ElfN.Shdr, si: Symbol.Index }, ehdr.shnum); @@ -1397,7 +1573,7 @@ fn loadObject( switch (section.shdr.type) { std.elf.SHT_NULL, std.elf.SHT_NOBITS => {}, else => if (section.shdr.offset + section.shdr.size > fl.size) - return diags.failParse(path, "bad section offset/size", .{}), + return diags.failParse(path, "bad section location", .{}), } } const shstrtab = shstrtab: { @@ -1421,7 +1597,7 @@ fn loadObject( std.elf.SHT_PROGBITS, std.elf.SHT_NOBITS => { if (section.shdr.name >= shstrtab.len) continue; const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0); - const parent_si = namedSection(name) orelse continue; + const parent_si = elf.namedSection(name) orelse continue; const ni = try elf.mf.addLastChildNode(gpa, parent_si.node(elf), .{ .size = section.shdr.size, .alignment = .fromByteUnits(std.math.ceilPowerOfTwoAssert( @@ -1598,6 +1774,84 @@ fn loadObject( }, } } +fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { + const comp = elf.base.comp; + const diags = &comp.link_diags; + const r = &fr.interface; + + log.debug("loadDso({f})", .{path.fmtEscapeString()}); + const ident = try r.peek(std.elf.EI.NIDENT); + if (!std.mem.eql(u8, ident, elf.mf.contents[0..std.elf.EI.NIDENT])) + return diags.failParse(path, "bad ident", .{}); + const target_endian = elf.targetEndian(); + switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| { + const ElfN = class.ElfN(); + const ehdr = try r.peekStruct(ElfN.Ehdr, target_endian); + if (ehdr.type != .DYN) return diags.failParse(path, "unsupported dso type", .{}); + if (ehdr.machine != elf.ehdrField(.machine)) + return diags.failParse(path, "bad machine", .{}); + if (ehdr.phoff == 0 or ehdr.phnum <= 1) + return diags.failParse(path, "no program headers", .{}); + try fr.seekTo(ehdr.phoff); + const dynamic_ph = for (0..ehdr.phnum) |_| { + const ph = try r.peekStruct(ElfN.Phdr, target_endian); + try r.discardAll(ehdr.phentsize); + switch (ph.type) { + else => {}, + std.elf.PT_DYNAMIC => break ph, + } + } else return diags.failParse(path, "no dynamic segment", .{}); + const dynnum = std.math.divExact( + u32, + @intCast(dynamic_ph.filesz), + @sizeOf(ElfN.Addr) * 2, + ) catch return diags.failParse( + path, + "dynamic segment filesz (0x{x}) is not a multiple of entsize (0x{x})", + .{ dynamic_ph.filesz, @sizeOf(ElfN.Addr) * 2 }, + ); + var strtab: ?ElfN.Addr = null; + var strsz: ?ElfN.Addr = null; + var soname: ?ElfN.Addr = null; + try fr.seekTo(dynamic_ph.offset); + for (0..dynnum) |_| { + const key = try r.takeInt(ElfN.Addr, target_endian); + const value = try r.takeInt(ElfN.Addr, target_endian); + switch (key) { + else => {}, + std.elf.DT_STRTAB => strtab = value, + std.elf.DT_STRSZ => strsz = value, + std.elf.DT_SONAME => soname = value, + } + } + if (strtab == null or soname == null) + return elf.loadDsoExact(std.fs.path.basename(path.sub_path)); + if (strsz) |size| if (soname.? >= size) + return diags.failParse(path, "bad soname string", .{}); + try fr.seekTo(ehdr.phoff); + const ph = for (0..ehdr.phnum) |_| { + const ph = try r.peekStruct(ElfN.Phdr, target_endian); + try r.discardAll(ehdr.phentsize); + switch (ph.type) { + else => {}, + std.elf.PT_LOAD => if (strtab.? >= ph.vaddr and + strtab.? + (strsz orelse 0) <= ph.vaddr + ph.filesz) break ph, + } + } else return diags.failParse(path, "strtab not part of a loaded segment", .{}); + try fr.seekTo(strtab.? + soname.? - ph.vaddr + ph.offset); + return elf.loadDsoExact(r.peekSentinel(0) catch |err| switch (err) { + error.StreamTooLong => return diags.failParse(path, "soname too lang", .{}), + else => |e| return e, + }); + }, + } +} +fn loadDsoExact(elf: *Elf, name: []const u8) !void { + log.debug("loadDsoExact({f})", .{std.zig.fmtString(name)}); + try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, name), {}); +} pub fn prelink(elf: *Elf, prog_node: std.Progress.Node) !void { _ = prog_node; @@ -1624,6 +1878,66 @@ fn prelinkInner(elf: *Elf) !void { .member = null, .si = si, }; + + if (elf.si.dynamic != .null) switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |ct_class| { + const ElfN = ct_class.ElfN(); + const needed_len = elf.needed.count(); + const dynamic_len = needed_len + @intFromBool(elf.options.soname != null) + 5; + const dynamic_size: u32 = @intCast(@sizeOf(ElfN.Addr) * 2 * dynamic_len); + const dynamic_ni = elf.si.dynamic.node(elf); + try dynamic_ni.resize(&elf.mf, gpa, dynamic_size); + const sec_dynamic = dynamic_ni.slice(&elf.mf); + const dynamic_entries: [][2]ElfN.Addr = @ptrCast(@alignCast(sec_dynamic)); + var dynamic_index: usize = 0; + for ( + dynamic_entries[dynamic_index..][0..needed_len], + elf.needed.keys(), + ) |*dynamic_entry, needed| dynamic_entry.* = .{ std.elf.DT_NEEDED, needed }; + dynamic_index += needed_len; + if (elf.options.soname) |soname| { + dynamic_entries[dynamic_index] = .{ std.elf.DT_SONAME, try elf.string(.dynstr, soname) }; + dynamic_index += 1; + } + dynamic_entries[dynamic_index..][0..5].* = .{ + .{ std.elf.DT_SYMTAB, 0 }, + .{ std.elf.DT_SYMENT, @sizeOf(ElfN.Sym) }, + .{ std.elf.DT_STRTAB, 0 }, + .{ std.elf.DT_STRSZ, 0 }, + .{ std.elf.DT_NULL, 0 }, + }; + dynamic_index += 5; + assert(dynamic_index == dynamic_len); + if (elf.targetEndian() != native_endian) for (dynamic_entries) |*dynamic_entry| + std.mem.byteSwapAllFields(@TypeOf(dynamic_entry.*), dynamic_entry); + + const dynamic_sym = elf.si.dynamic.get(elf); + assert(dynamic_sym.loc_relocs == .none); + dynamic_sym.loc_relocs = @enumFromInt(elf.relocs.items.len); + try elf.addReloc( + elf.si.dynamic, + @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 5) + 1), + elf.si.dynsym, + 0, + .absAddr(elf), + ); + try elf.addReloc( + elf.si.dynamic, + @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 3) + 1), + elf.si.dynstr, + 0, + .absAddr(elf), + ); + try elf.addReloc( + elf.si.dynamic, + @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 2) + 1), + elf.si.dynstr, + 0, + .sizeAddr(elf), + ); + }, + }; } pub fn getNavVAddr( @@ -1649,13 +1963,7 @@ pub fn getVAddr(elf: *Elf, reloc_info: link.File.RelocInfo, target_si: Symbol.In reloc_info.offset, target_si, reloc_info.addend, - switch (elf.ehdrField(.machine)) { - else => unreachable, - .AARCH64 => .{ .AARCH64 = .ABS64 }, - .PPC64 => .{ .PPC64 = .ADDR64 }, - .RISCV => .{ .RISCV = .@"64" }, - .X86_64 => .{ .X86_64 = .@"64" }, - }, + .absAddr(elf), ); return switch (elf.symPtr(target_si)) { inline else => |sym| elf.targetLoad(&sym.value), @@ -1665,11 +1973,16 @@ pub fn getVAddr(elf: *Elf, reloc_info: link.File.RelocInfo, target_si: Symbol.In fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { name: []const u8 = "", type: std.elf.Word = std.elf.SHT_NULL, - size: std.elf.Word = 0, flags: std.elf.SHF = .{}, + size: std.elf.Word = 0, addralign: std.mem.Alignment = .@"1", entsize: std.elf.Word = 0, }) !Symbol.Index { + switch (opts.type) { + std.elf.SHT_NULL => assert(opts.size == 0), + std.elf.SHT_PROGBITS => assert(opts.size > 0), + else => {}, + } const gpa = elf.base.comp.gpa; try elf.nodes.ensureUnusedCapacity(gpa, 1); try elf.symtab.ensureUnusedCapacity(gpa, 1); @@ -1683,16 +1996,23 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { break :shndx .{ shndx, elf.targetLoad(&ehdr.shentsize) * shnum }; }, }; - try Node.Known.shdr.resize(&elf.mf, gpa, shdr_size); + try elf.ni.shdr.resize(&elf.mf, gpa, shdr_size); const ni = try elf.mf.addLastChildNode(gpa, segment_ni, .{ .alignment = opts.addralign, .size = opts.size, - .moved = true, + .resized = opts.size > 0, }); const si = elf.addSymbolAssumeCapacity(); elf.nodes.appendAssumeCapacity(.{ .section = si }); si.get(elf).ni = ni; - try si.init(elf, .{ .size = opts.size, .type = .SECTION, .shndx = shndx }); + const addr = elf.computeNodeVAddr(ni); + const offset = ni.fileLocation(&elf.mf, false).offset; + try si.init(elf, .{ + .value = addr, + .size = opts.size, + .type = .SECTION, + .shndx = shndx, + }); switch (elf.shdrSlice()) { inline else => |shdr| { const sh = &shdr[shndx]; @@ -1700,8 +2020,8 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { .name = shstrtab_entry, .type = opts.type, .flags = .{ .shf = opts.flags }, - .addr = 0, - .offset = 0, + .addr = @intCast(addr), + .offset = @intCast(offset), .size = opts.size, .link = 0, .info = 0, @@ -1733,7 +2053,7 @@ fn linkSections(elf: *Elf, si: Symbol.Index, link_si: Symbol.Index) !void { } fn sectionName(elf: *Elf, si: Symbol.Index) [:0]const u8 { - const name = Symbol.Index.shstrtab.node(elf).slice(&elf.mf)[switch (elf.shdrSlice()) { + const name = elf.si.shstrtab.node(elf).slice(&elf.mf)[switch (elf.shdrSlice()) { inline else => |shndx, class| elf.targetLoad( &shndx[elf.targetLoad(&@field(elf.symPtr(si), @tagName(class)).shndx)].name, ), @@ -1741,12 +2061,12 @@ fn sectionName(elf: *Elf, si: Symbol.Index) [:0]const u8 { return name[0..std.mem.indexOfScalar(u8, name, 0).? :0]; } -fn string(elf: *Elf, comptime section: enum { shstrtab, strtab }, key: []const u8) !u32 { +fn string(elf: *Elf, comptime section: enum { shstrtab, strtab, dynstr }, key: []const u8) !u32 { if (key.len == 0) return 0; return @field(elf, @tagName(section)).get( elf.base.comp.gpa, &elf.mf, - @field(Symbol.Index, @tagName(section)).node(elf), + @field(elf.si, @tagName(section)).node(elf), key, ); } @@ -2144,7 +2464,7 @@ fn flushUav( switch (sym.ni) { .none => { try elf.nodes.ensureUnusedCapacity(gpa, 1); - const ni = try elf.mf.addLastChildNode(gpa, Symbol.Index.data.node(elf), .{ + const ni = try elf.mf.addLastChildNode(gpa, elf.si.data.node(elf), .{ .alignment = uav_align.toStdMem(), .moved = true, }); @@ -2256,9 +2576,9 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { return error.EndOfStream; } -fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { +fn flushFileOffset(elf: *Elf, ni: MappedFile.Node.Index) !void { switch (elf.getNode(ni)) { - .file => unreachable, + else => unreachable, .ehdr => assert(ni.fileLocation(&elf.mf, false).offset == 0), .shdr => switch (elf.ehdrPtr()) { inline else => |ehdr| elf.targetStore( @@ -2266,32 +2586,61 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { @intCast(ni.fileLocation(&elf.mf, false).offset), ), }, - .segment => |phndx| switch (elf.phdrSlice()) { - inline else => |phdr, class| { - const ph = &phdr[phndx]; - elf.targetStore(&ph.offset, @intCast(ni.fileLocation(&elf.mf, false).offset)); - switch (elf.targetLoad(&ph.type)) { - else => unreachable, - std.elf.PT_NULL, std.elf.PT_LOAD => return, - std.elf.PT_DYNAMIC, std.elf.PT_INTERP => {}, - std.elf.PT_PHDR => @field(elf.ehdrPtr(), @tagName(class)).phoff = ph.offset, - std.elf.PT_TLS => {}, - } - elf.targetStore(&ph.vaddr, @intCast(elf.computeNodeVAddr(ni))); - ph.paddr = ph.vaddr; - }, + .segment => |phndx| { + switch (elf.phdrSlice()) { + inline else => |phdr| elf.targetStore( + &phdr[phndx].offset, + @intCast(ni.fileLocation(&elf.mf, false).offset), + ), + } + var child_it = ni.children(&elf.mf); + while (child_it.next()) |child_ni| try elf.flushFileOffset(child_ni); }, .section => |si| switch (elf.shdrSlice()) { - inline else => |shdr, class| { - const sym = @field(elf.symPtr(si), @tagName(class)); - const sh = &shdr[elf.targetLoad(&sym.shndx)]; - elf.targetStore(&sh.offset, @intCast(ni.fileLocation(&elf.mf, false).offset)); - const flags = elf.targetLoad(&sh.flags).shf; - if (flags.ALLOC) { - elf.targetStore(&sh.addr, @intCast(elf.computeNodeVAddr(ni))); - if (!flags.TLS) sym.value = sh.addr; - } - }, + inline else => |shdr, class| elf.targetStore( + &shdr[elf.targetLoad(&@field(elf.symPtr(si), @tagName(class)).shndx)].offset, + @intCast(ni.fileLocation(&elf.mf, false).offset), + ), + }, + } +} + +fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { + switch (elf.getNode(ni)) { + .file => unreachable, + .ehdr, .shdr => try elf.flushFileOffset(ni), + .segment => |phndx| { + try elf.flushFileOffset(ni); + switch (elf.phdrSlice()) { + inline else => |phdr, class| { + const ph = &phdr[phndx]; + switch (elf.targetLoad(&ph.type)) { + else => unreachable, + std.elf.PT_NULL, std.elf.PT_LOAD => return, + std.elf.PT_DYNAMIC, std.elf.PT_INTERP => {}, + std.elf.PT_PHDR => @field(elf.ehdrPtr(), @tagName(class)).phoff = ph.offset, + std.elf.PT_TLS => {}, + } + elf.targetStore(&ph.vaddr, @intCast(elf.computeNodeVAddr(ni))); + ph.paddr = ph.vaddr; + }, + } + }, + .section => |si| { + try elf.flushFileOffset(ni); + const addr = elf.computeNodeVAddr(ni); + switch (elf.shdrSlice()) { + inline else => |shdr, class| { + const sym = @field(elf.symPtr(si), @tagName(class)); + const sh = &shdr[elf.targetLoad(&sym.shndx)]; + const flags = elf.targetLoad(&sh.flags).shf; + if (flags.ALLOC) { + elf.targetStore(&sh.addr, @intCast(addr)); + sym.value = sh.addr; + } + }, + } + si.flushMoved(elf, addr); }, .input_section => |isi| { const old_addr = switch (elf.symPtr(isi.symbol(elf))) { @@ -2380,11 +2729,11 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void { else => unreachable, std.elf.SHT_NULL => if (size > 0) elf.targetStore(&sh.type, std.elf.SHT_PROGBITS), std.elf.SHT_PROGBITS => if (size == 0) elf.targetStore(&sh.type, std.elf.SHT_NULL), - std.elf.SHT_SYMTAB => elf.targetStore( + std.elf.SHT_SYMTAB, std.elf.SHT_DYNSYM => elf.targetStore( &sh.info, @intCast(@divExact(size, elf.targetLoad(&sh.entsize))), ), - std.elf.SHT_STRTAB => {}, + std.elf.SHT_STRTAB, std.elf.SHT_DYNAMIC => {}, } }, }, diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 44b889365c..75aad965d9 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -808,7 +808,6 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { const link_mode = comp.config.link_mode; const is_dyn_lib = link_mode == .dynamic and is_lib; const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; - const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib; const target = &comp.root_mod.resolved_target.result; const compiler_rt_path: ?Cache.Path = blk: { if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; @@ -1070,12 +1069,12 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { } } - if (have_dynamic_linker and - (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker)) - { + if (output_mode == .Exe and link_mode == .dynamic) { if (target.dynamic_linker.get()) |dynamic_linker| { - try argv.append("-dynamic-linker"); + try argv.append("--dynamic-linker"); try argv.append(dynamic_linker); + } else { + try argv.append("--no-dynamic-linker"); } } diff --git a/src/main.zig b/src/main.zig index 03d42ba898..dd8c4695e6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -558,6 +558,7 @@ const usage_build_generic = \\ --enable-new-dtags Use the new behavior for dynamic tags (RUNPATH) \\ --disable-new-dtags Use the old behavior for dynamic tags (RPATH) \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) + \\ --no-dynamic-linker Do not set any dynamic interpreter path \\ --sysroot [path] Set the system root directory (usually /) \\ --version [ver] Dynamic library semver \\ -fentry Enable entry point with default symbol name @@ -1301,6 +1302,8 @@ fn buildOutputType( mod_opts.optimize_mode = parseOptimizeMode(rest); } else if (mem.eql(u8, arg, "--dynamic-linker")) { create_module.dynamic_linker = args_iter.nextOrFatal(); + } else if (mem.eql(u8, arg, "--no-dynamic-linker")) { + create_module.dynamic_linker = ""; } else if (mem.eql(u8, arg, "--sysroot")) { const next_arg = args_iter.nextOrFatal(); create_module.sysroot = next_arg; @@ -2418,6 +2421,11 @@ fn buildOutputType( mem.eql(u8, arg, "-dynamic-linker")) { create_module.dynamic_linker = linker_args_it.nextOrFatal(); + } else if (mem.eql(u8, arg, "-I") or + mem.eql(u8, arg, "--no-dynamic-linker") or + mem.eql(u8, arg, "-no-dynamic-linker")) + { + create_module.dynamic_linker = ""; } else if (mem.eql(u8, arg, "-E") or mem.eql(u8, arg, "--export-dynamic") or mem.eql(u8, arg, "-export-dynamic")) @@ -3191,13 +3199,14 @@ fn buildOutputType( const resolved_soname: ?[]const u8 = switch (soname) { .yes => |explicit| explicit, .no => null, - .yes_default_value => switch (target.ofmt) { - .elf => if (have_version) + .yes_default_value => if (create_module.resolved_options.output_mode == .Lib and + create_module.resolved_options.link_mode == .dynamic and target.ofmt == .elf) + if (have_version) try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ root_name, version.major }) else - try std.fmt.allocPrint(arena, "lib{s}.so", .{root_name}), - else => null, - }, + try std.fmt.allocPrint(arena, "lib{s}.so", .{root_name}) + else + null, }; const emit_bin_resolved: Compilation.CreateOptions.Emit = switch (emit_bin) { @@ -3646,7 +3655,11 @@ fn buildOutputType( try test_exec_args.append(arena, try std.fmt.allocPrint(arena, "-mcpu={s}", .{mcpu})); } if (create_module.dynamic_linker) |dl| { - try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl }); + if (dl.len > 0) { + try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl }); + } else { + try test_exec_args.append(arena, "--no-dynamic-linker"); + } } try test_exec_args.append(arena, null); // placeholder for the path of the emitted C source file } @@ -3793,7 +3806,7 @@ fn createModule( .result = target, .is_native_os = target_query.isNativeOs(), .is_native_abi = target_query.isNativeAbi(), - .is_explicit_dynamic_linker = !target_query.dynamic_linker.eql(.none), + .is_explicit_dynamic_linker = target_query.dynamic_linker != null, }; }; @@ -3965,6 +3978,7 @@ fn createModule( error.WasiExecModelRequiresWasi => fatal("only WASI OS targets support execution model", .{}), error.SharedMemoryIsWasmOnly => fatal("only WebAssembly CPU targets support shared memory", .{}), error.ObjectFilesCannotShareMemory => fatal("object files cannot share memory", .{}), + error.ObjectFilesCannotSpecifyDynamicLinker => fatal("object files cannot specify --dynamic-linker", .{}), error.SharedMemoryRequiresAtomicsAndBulkMemory => fatal("shared memory requires atomics and bulk_memory CPU features", .{}), error.ThreadsRequireSharedMemory => fatal("threads require shared memory", .{}), error.EmittingLlvmModuleRequiresLlvmBackend => fatal("emitting an LLVM module requires using the LLVM backend", .{}), @@ -3973,6 +3987,7 @@ fn createModule( error.EmittingBinaryRequiresLlvmLibrary => fatal("producing machine code via LLVM requires using the LLVM library", .{}), error.LldIncompatibleObjectFormat => fatal("using LLD to link {s} files is unsupported", .{@tagName(target.ofmt)}), error.LldCannotIncrementallyLink => fatal("self-hosted backends do not support linking with LLD", .{}), + error.LldCannotSpecifyDynamicLinkerForSharedLibraries => fatal("LLD does not support --dynamic-linker on shared libraries", .{}), error.LtoRequiresLld => fatal("LTO requires using LLD", .{}), error.SanitizeThreadRequiresLibCpp => fatal("thread sanitization is (for now) implemented in C++, so it requires linking libc++", .{}), error.LibCRequiresLibUnwind => fatal("libc of the specified target requires linking libunwind", .{}), @@ -3984,6 +3999,7 @@ fn createModule( error.TargetCannotStaticLinkExecutables => fatal("static linking of executables unavailable on the specified target", .{}), error.LibCRequiresDynamicLinking => fatal("libc of the specified target requires dynamic linking", .{}), error.SharedLibrariesRequireDynamicLinking => fatal("using shared libraries requires dynamic linking", .{}), + error.DynamicLinkingWithLldRequiresSharedLibraries => fatal("dynamic linking with lld requires at least one shared library", .{}), error.ExportMemoryAndDynamicIncompatible => fatal("exporting memory is incompatible with dynamic linking", .{}), error.DynamicLibraryPrecludesPie => fatal("dynamic libraries cannot be position independent executables", .{}), error.TargetRequiresPie => fatal("the specified target requires position independent executables", .{}),