From ad06fe07c531b76f99d655680b240916561e21e1 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sun, 24 May 2026 11:41:30 +0100 Subject: [PATCH] Elf2: non-trivial GOT, better dynamic linking support It's a bit tricky to give this commit a clear description, sorry---it's a lot of semi-related enhancements. The main things are probably: * Implement creating arbitrary GOT entries, so we can finally resolve GOT relocations (e.g. `R_X86_64_[REX_]GOTPCREL[X]`) * Introduce a new representation for relocations which is more memory-efficient, can handle GOT relocations, and (theoretically) helps to abstract over different target machines * Start emitting runtime relocations when a relocation is not resolvable, and add the `DT_TEXTREL` entry to `.dynamic` when requires * "Free" PLT slots when a symbol becomes defined, and allow reusing those free slots * Implement the majority of x86_64 relocation types The actual impact of these changes is that this linker is now relatively functional (ignoring debug information and stack unwinding information, which is still unimplemented). In particular, it is able to successfully link the Zig compiler against LLVM, static *or* dynamic. There is one caveat to this, which is that because we are not yet emitting `R_X86_64_COPY` relocations, errors like this one are possible when running a compiler dynamically linked with Elf2: ./zig-dynamic-from-elf2/bin/zig: Symbol `__libc_single_threaded' causes overflow in R_X86_64_PC32 relocation In some cases, this is unproblematic, but in others, it will cause random crashes when calling into the LLVM API. You can work around this by passing `-DCMAKE_POSITION_INDEPENDENT_CODE=ON` to CMake so that libzigcpp is built as PIC. Resolves: https://codeberg.org/ziglang/zig/issues/30780 --- src/link/Elf2.zig | 2643 +++++++++++++++++++++++++++++++-------------- 1 file changed, 1811 insertions(+), 832 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 68e07062de65f3212b4e6f8eb025f49ae37f02f3..747252591f1a1e284310a86c2d690fcdf3b5670a 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -34,6 +34,8 @@ shndx: struct { dynstr: Section.Index, dynamic: Section.Index, tdata: Section.Index, + rela_dyn: Section.Index, + rela_plt: Section.Index, // These sections are created only as needed, and are initially `.UNDEF`. init_array: Section.Index, fini_array: Section.Index, @@ -65,13 +67,23 @@ dso_globals: std.array_hash_map.Auto(String(.strtab), std.elf.STT), shstrtab: StringTable, strtab: StringTable, dynstr: StringTable, -got: struct { - len: u32, - tlsld: GotIndex, - plt: std.AutoArrayHashMapUnmanaged(Symbol.Id, void), -}, -first_plt_reloc: Reloc.Index, -first_dynamic_reloc: Reloc.Index, + +/// Indices map 1--1 to indices into the actual `.got` section. +/// +/// Value is the output relocation in `.rela.dyn` for the GOT entry. +got: std.array_hash_map.Auto(GotKey, Section.RelaIndex.Optional), +/// Indices map 1--1 to indices into the actual `.got.plt` section. These also equal indices into +/// the relocations in `.rela.plt`, because every PLT entry has one output relocation (if a runtime +/// relocation is no longer necessary, then neither is the corresponding PLT entry!). +/// +/// PLT entries in this map may be "dead", meaning the PLT entry has been deemed unnecessary so is +/// available for reuse---see `Elf.pltEntryIsDead`. Such entries must not be targeted by relocs. +plt: std.array_hash_map.Auto(Symbol.Id, void), +/// The `.plt` section contains zero or more symbol relocations starting at this index. +plt_first_symbol_reloc: SymbolReloc.Index, +/// The `.dynamic` section contains zero or more symbol relocations starting at this index. +dynamic_first_symbol_reloc: SymbolReloc.Index, + needed: std.AutoArrayHashMapUnmanaged(String(.dynstr), void), inputs: std.ArrayList(struct { path: std.Build.Cache.Path, @@ -81,31 +93,42 @@ inputs: std.ArrayList(struct { input_sections: std.ArrayList(InputSection), input_section_pending_index: u32, navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, struct { - /// The start index of the contiguous sequence of relocations in this NAV. - first_reloc: Reloc.Index, lsi: Symbol.LocalIndex, + /// The start index of the contiguous sequence of symbol relocations in this NAV. + first_symbol_reloc: SymbolReloc.Index, + /// The start index of the contiguous sequence of GOT relocations in this NAV. + first_got_reloc: GotReloc.Index, }), uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, struct { - /// The start index of the contiguous sequence of relocations in this UAV. - first_reloc: Reloc.Index, lsi: Symbol.LocalIndex, + /// The start index of the contiguous sequence of symbol relocations in this UAV. + first_symbol_reloc: SymbolReloc.Index, + // No `first_got_reloc` field because a UAV never contains GOT relocations. }), lazy: std.EnumArray(link.File.LazySymbol.Kind, struct { map: std.AutoArrayHashMapUnmanaged(InternPool.Index, struct { - /// The start index of the contiguous sequence of relocations in this lazy code/data. - first_reloc: Reloc.Index, lsi: Symbol.LocalIndex, + /// The start index of the contiguous sequence of symbol relocations in this lazy code/data. + first_symbol_reloc: SymbolReloc.Index, + /// The start index of the contiguous sequence of GOT relocations in this lazy code/data. + first_got_reloc: GotReloc.Index, }), pending_index: u32, }), pending_uavs: std.ArrayList(Node.UavMapIndex), -relocs: std.ArrayList(Reloc), +symbol_relocs: std.ArrayList(SymbolReloc), +got_relocs: std.ArrayList(GotReloc), +/// Set of relocations which must be re-applied if the size of the TLS segment changes. +tls_size_symbol_relocs: std.array_hash_map.Auto(SymbolReloc.Index, void), /// Index matches the index into `shdrs`. section_by_name: std.array_hash_map.Auto(String(.shstrtab), void), - /// Key is the name of a global symbol which has been moved to a new symtab index. Any relocation /// entries which target that symbol must be updated to reference the correct symbol index. changed_symtab_index: std.array_hash_map.Auto(String(.strtab), void), +/// Counts how many relocations are currently in `.rela.dyn` which would require a `DT_TEXTREL` +/// entry in the `.dynamic` section. This allows adding `DT_TEXTREL` to the output `.dynamic` +/// section in `flush` only when it is actually necessary. See also `nodeRequiresTextrel`. +textrel_count: u32, const_prog_node: std.Progress.Node, synth_prog_node: std.Progress.Node, @@ -120,21 +143,21 @@ const Node = union(enum) { shdr, /// Cannot contain relocations. segment: u32, - /// The section '.plt' may contain relocations via `elf.first_plt_reloc`. + /// The section '.plt' may contain relocations via `elf.plt_first_symbol_reloc`. /// - /// The section '.dynamic' may contain relocations via `elf.first_dynamic_reloc`. + /// The section '.dynamic' may contain relocations via `elf.dynamic_first_symbol_reloc`. /// /// Otherwise, cannot contain relocations. section: Section.Index, - /// May contain relocations through the `first_reloc` field in `elf.input_sections`. + /// May contain relocations. input_section: InputSection.Index, - /// May contain relocations through the `first_reloc` field in `elf.navs`. + /// May contain relocations. nav: NavMapIndex, - /// May contain relocations through the `first_reloc` field in `elf.uavs`. + /// May contain relocations. uav: UavMapIndex, - /// May contain relocations through the `first_reloc` field in `elf.lazy.map`. + /// May contain relocations. lazy_code: LazyMapRef.Index(.code), - /// May contain relocations through the `first_reloc` field in `elf.lazy.map`. + /// May contain relocations. lazy_const_data: LazyMapRef.Index(.const_data), pub const InputIndex = enum(u32) { @@ -176,8 +199,11 @@ const Node = union(enum) { return elf.navs.values()[@intFromEnum(nmi)].lsi; } - fn firstReloc(nmi: NavMapIndex, elf: *const Elf) Reloc.Index { - return elf.navs.values()[@intFromEnum(nmi)].first_reloc; + fn firstSymbolReloc(nmi: NavMapIndex, elf: *const Elf) SymbolReloc.Index { + return elf.navs.values()[@intFromEnum(nmi)].first_symbol_reloc; + } + fn firstGotReloc(nmi: NavMapIndex, elf: *const Elf) GotReloc.Index { + return elf.navs.values()[@intFromEnum(nmi)].first_got_reloc; } }; @@ -192,8 +218,13 @@ const Node = union(enum) { return elf.uavs.values()[@intFromEnum(umi)].lsi; } - fn firstReloc(umi: UavMapIndex, elf: *const Elf) Reloc.Index { - return elf.uavs.values()[@intFromEnum(umi)].first_reloc; + fn firstSymbolReloc(umi: UavMapIndex, elf: *const Elf) SymbolReloc.Index { + return elf.uavs.values()[@intFromEnum(umi)].first_symbol_reloc; + } + fn firstGotReloc(umi: UavMapIndex, elf: *const Elf) GotReloc.Index { + _ = umi; + _ = elf; + return .none; } }; @@ -217,8 +248,11 @@ const Node = union(enum) { return lmi.ref().symbol(elf); } - fn firstReloc(lmi: @This(), elf: *const Elf) Reloc.Index { - return lmi.ref().firstReloc(elf); + fn firstSymbolReloc(lmi: @This(), elf: *const Elf) SymbolReloc.Index { + return elf.lazy.getPtrConst(kind).map.values()[@intFromEnum(lmi)].first_symbol_reloc; + } + fn firstGotReloc(lmi: @This(), elf: *const Elf) GotReloc.Index { + return elf.lazy.getPtrConst(kind).map.values()[@intFromEnum(lmi)].first_got_reloc; } }; } @@ -230,10 +264,6 @@ const Node = union(enum) { pub fn symbol(lmr: LazyMapRef, elf: *const Elf) Symbol.LocalIndex { return elf.lazy.getPtrConst(lmr.kind).map.values()[lmr.index].lsi; } - - fn firstReloc(lmr: LazyMapRef, elf: *const Elf) Reloc.Index { - return elf.lazy.getPtrConst(lmr.kind).map.values()[lmr.index].first_reloc; - } }; pub const Known = struct { @@ -269,8 +299,10 @@ const InputSection = struct { vaddr: u64, /// The node corresponding to this input section. node: MappedFile.Node.Index, - /// The start index of the contiguous sequence of relocations in this input section. - first_reloc: Reloc.Index, + /// The start index of the contiguous sequence of symbol relocations in this input section. + first_symbol_reloc: SymbolReloc.Index, + /// The start index of the contiguous sequence of GOT relocations in this input section. + first_got_reloc: GotReloc.Index, const Index = enum(u32) { _, @@ -304,21 +336,53 @@ const Section = struct { /// /// If the section does not have flag `std.elf.SHF.ALLOC`, this is `.null`. lsi: Symbol.LocalIndex, - rela_shndx: Section.Index, - rela_free: RelIndex, + rela: union { + /// This field is active if and only if this section is *not* a `SHT_RELA` section. + /// + /// This field's value refers to this section's corresponding relocation section, if it + /// currently has one. If this section does not currently have a relocation section, the + /// value is `.UNDEF`. + /// + /// This field is only ever non-`.UNDEF` when emitting a relocatable (`ET_REL`). While there + /// are also output relocations in DSOs, they are all placed in the `.rela.dyn` + /// (`elf.shdnx.rela_dyn`) and `.rela.plt` (`elf.shndx.rela_plt`) sections, rather than + /// having separate relocation sections for each section. + shndx: Section.Index, - pub const RelIndex = enum(u32) { + /// This field is active if and only if this section *is* a `SHT_RELA` section. + /// + /// This is the head of a single-linked list of free `ElfN.Rela` entries in this section. + /// Entries in this list have `info.type` set to `R_*_NONE`, have `info.sym` set to 0, and + /// have `offset` set to `@enumFromInt(next)` where `next` is `RelaIndex.Optional`. Also, + /// `addend` is set to the length of the list starting from this point; so the last node in + /// the list has `addend = 1`, the one before it has `addend = 2`, etc. This is so that the + /// head node always contains the current length of the list. + /// + /// It would be okay to store these values (in the `offset` and `addend` fields) in the + /// compiler's host endianness, because they will never be read by other tooling. However, + /// we nonetheless use target endianness, because using host endianness would introduce an + /// unnecessary dependency of the output binary on the compiler's host architecture. + free_head: RelaIndex.Optional, + }, + + const RelaIndex = enum(u32) { none, _, - pub fn wrap(i: ?u32) RelIndex { - return @enumFromInt((i orelse return .none) + 1); - } - pub fn unwrap(ri: RelIndex) ?u32 { - return switch (ri) { - .none => null, - _ => @intFromEnum(ri) - 1, - }; + const Optional = enum(u32) { + none = std.math.maxInt(u32), + _, + + fn unwrap(opt: RelaIndex.Optional) ?RelaIndex { + return switch (opt) { + .none => null, + _ => @enumFromInt(@intFromEnum(opt)), + }; + } + }; + + fn toOptional(i: RelaIndex) RelaIndex.Optional { + return @enumFromInt(@intFromEnum(i)); } }; @@ -390,9 +454,690 @@ const Section = struct { inline else => |shdr| elf.targetStore(&shdr.name, @intFromEnum(shstrtab_entry)), } } + + /// Asserts that `shndx` is a `SHT_RELA` section and ensures that its node has enough unused + /// space to hold `n` additional `ElfN.Rela` entries. + fn relaEnsureAdditionalCapacity(rela_shndx: Index, elf: *Elf, n: usize) !void { + const node = rela_shndx.get(elf).ni; + const need_size: u64 = switch (elf.shdrPtr(rela_shndx)) { + inline else => |shdr, class| need_size: { + assert(elf.targetLoad(&shdr.type) == .RELA); + const cur_size = elf.targetLoad(&shdr.size); + const ent_size = @sizeOf(class.ElfN().Rela); + assert(elf.targetLoad(&shdr.entsize) == ent_size); + const free_len: u32 = free_len: { + const opt_free_head = rela_shndx.get(elf).rela.free_head; + const free_head = opt_free_head.unwrap() orelse break :free_len 0; + const relas: []const class.ElfN().Rela = @ptrCast(@alignCast( + node.slice(&elf.mf)[0..@intCast(cur_size)], + )); + const free_len = elf.targetLoad(&relas[@intFromEnum(free_head)].addend); + assert(free_len > 0); + break :free_len @intCast(free_len); + }; + const need_additional = n -| free_len; + break :need_size cur_size + need_additional * ent_size; + }, + }; + _, const cur_node_size = node.location(&elf.mf).resolve(&elf.mf); + if (need_size > cur_node_size) { + const gpa = elf.base.comp.gpa; + try node.resize(&elf.mf, gpa, need_size +| need_size / MappedFile.growth_factor); + } + } + + /// Asserts that `shndx` is a `SHT_RELA` section and deletes the `ElfN.Rela` entry at the + /// given `index` in it. The entry is added to the free-list for reuse later. Asserts that + /// the relocation entry at `index` is not already free. + fn relaDeleteOne(rela_shndx: Index, elf: *Elf, index: RelaIndex) void { + switch (elf.shdrPtr(rela_shndx)) { + inline else => |shdr, class| { + assert(elf.targetLoad(&shdr.type) == .RELA); + assert(elf.targetLoad(&shdr.entsize) == @sizeOf(class.ElfN().Rela)); + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + rela_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast(elf.targetLoad(&shdr.size))], + )); + const opt_free_head = rela_shndx.get(elf).rela.free_head; + const old_free_len: u32 = free_len: { + const free_head = opt_free_head.unwrap() orelse break :free_len 0; + const free_len = elf.targetLoad(&relas[@intFromEnum(free_head)].addend); + assert(free_len > 0); + break :free_len @intCast(free_len); + }; + const none_reloc_type = MachineRelocType.none(elf).unwrap(elf); + { + const old_type = elf.targetLoad(&relas[@intFromEnum(index)].info).type; + assert(old_type != none_reloc_type); // bug: `index` is already in the free-list + } + relas[@intFromEnum(index)] = .{ + .offset = @intFromEnum(opt_free_head), // next + .info = .{ + .type = @intCast(none_reloc_type), + .sym = 0, + }, + .addend = @intCast(old_free_len + 1), // list length + }; + if (elf.targetEndian() != native_endian) { + std.mem.byteSwapAllFields(class.ElfN().Rela, &relas[@intFromEnum(index)]); + } + }, + } + rela_shndx.get(elf).rela.free_head = index.toOptional(); + } + + /// Asserts that `shndx` is a `SHT_RELA` section and adds a new `ElfN.Rela` entry to it with + /// the given field values. Returns the index of the populated entry. Asserts that capacity + /// for this operation was already guaranteed using `relaEnsureAdditionalCapacity`. + fn relaAddOneAssumeCapacity(rela_shndx: Index, elf: *Elf, opts: struct { + type: MachineRelocType, + offset: u64, + /// This is a raw `u32` because whether this is an index into `.symtab` (`Symbol.Index`) + /// or an index into `.dynsym` is contextual. + raw_sym_index: u32, + addend: i64, + }) RelaIndex { + switch (elf.shdrPtr(rela_shndx)) { + inline else => |shdr, class| { + assert(elf.targetLoad(&shdr.type) == .RELA); + const ent_size = @sizeOf(class.ElfN().Rela); + assert(elf.targetLoad(&shdr.entsize) == ent_size); + const new_index: RelaIndex = if (rela_shndx.get(elf).rela.free_head.unwrap()) |free_head| new_index: { + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + rela_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast(elf.targetLoad(&shdr.size))], + )); + const next: RelaIndex.Optional = @enumFromInt(elf.targetLoad( + &relas[@intFromEnum(free_head)].offset, + )); + rela_shndx.get(elf).rela.free_head = next; + + const old_free_len: u32 = @intCast( + elf.targetLoad(&relas[@intFromEnum(free_head)].addend), + ); + const new_free_len: u32 = if (next.unwrap()) |i| @intCast( + elf.targetLoad(&relas[@intFromEnum(i)].addend), + ) else 0; + assert(new_free_len == old_free_len - 1); + + break :new_index free_head; + } else new_index: { + const old_size = elf.targetLoad(&shdr.size); + const new_size = old_size + ent_size; + elf.targetStore(&shdr.size, new_size); + if (rela_shndx == elf.shndx.rela_dyn) { + elf.updateDynamicEntry(std.elf.DT_RELASZ, new_size); + } else if (rela_shndx == elf.shndx.rela_plt) { + elf.updateDynamicEntry(std.elf.DT_PLTRELSZ, new_size); + } + break :new_index @enumFromInt(@divExact(old_size, ent_size)); + }; + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + rela_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast(elf.targetLoad(&shdr.size))], + )); + relas[@intFromEnum(new_index)] = .{ + .offset = @intCast(opts.offset), + .info = .{ + .type = @intCast(opts.type.unwrap(elf)), + .sym = @intCast(opts.raw_sym_index), + }, + .addend = @intCast(opts.addend), + }; + if (elf.targetEndian() != native_endian) { + std.mem.byteSwapAllFields(class.ElfN().Rela, &relas[@intFromEnum(new_index)]); + } + return new_index; + }, + } + } + + /// Asserts that `shndx` is a `SHT_RELA` section and updates the `info.sym` field of the + /// `ElfN.Rela` entry at the given index. As with `relaAddOneAssumeCapacity`, the symbol + /// index is a raw `u32`, because it may be an index into `.symtab` or an index into + /// `.dynsym`. Asserts that `index` is not in the free-list (i.e. is not deleted). + fn relaUpdateSym(rela_shndx: Index, elf: *Elf, index: RelaIndex, raw_sym_index: u32) void { + switch (elf.shdrPtr(rela_shndx)) { + inline else => |shdr, class| { + assert(elf.targetLoad(&shdr.type) == .RELA); + assert(elf.targetLoad(&shdr.entsize) == @sizeOf(class.ElfN().Rela)); + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + rela_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast(elf.targetLoad(&shdr.size))], + )); + const rela_info = elf.targetLoad(&relas[@intFromEnum(index)].info); + { + const none_reloc_type = MachineRelocType.none(elf).unwrap(elf); + assert(rela_info.type != none_reloc_type); // bug: `index` is in the free-list + } + elf.targetStore(&relas[@intFromEnum(index)].info, .{ + .type = rela_info.type, + .sym = @intCast(raw_sym_index), + }); + }, + } + } + + /// Asserts that `shndx` is a `SHT_RELA` section and updates the `offset` field of the + /// `ElfN.Rela` entry at the given index. Asserts that `index` is not in the free-list (i.e. + /// it is not deleted). + fn relaSetOffset(rela_shndx: Index, elf: *Elf, index: RelaIndex, new_offset: u64) void { + switch (elf.shdrPtr(rela_shndx)) { + inline else => |shdr, class| { + assert(elf.targetLoad(&shdr.type) == .RELA); + assert(elf.targetLoad(&shdr.entsize) == @sizeOf(class.ElfN().Rela)); + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + rela_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast(elf.targetLoad(&shdr.size))], + )); + { + const rela_info = elf.targetLoad(&relas[@intFromEnum(index)].info); + const none_reloc_type = MachineRelocType.none(elf).unwrap(elf); + assert(rela_info.type != none_reloc_type); // bug: `index` is in the free-list + } + elf.targetStore(&relas[@intFromEnum(index)].offset, @intCast(new_offset)); + }, + } + } + + /// Asserts that `shndx` is a `SHT_RELA` section and updates the `offset` field of the + /// `ElfN.Rela` entry at the given index, by subtracting `old_base` and adding `new_base`. + /// Asserts that `index` is not in the free-list (i.e. it is not deleted). + fn relaAdjustOffset(rela_shndx: Index, elf: *Elf, index: RelaIndex, old_base: u64, new_base: u64) void { + switch (elf.shdrPtr(rela_shndx)) { + inline else => |shdr, class| { + assert(elf.targetLoad(&shdr.type) == .RELA); + assert(elf.targetLoad(&shdr.entsize) == @sizeOf(class.ElfN().Rela)); + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + rela_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast(elf.targetLoad(&shdr.size))], + )); + { + const rela_info = elf.targetLoad(&relas[@intFromEnum(index)].info); + const none_reloc_type = MachineRelocType.none(elf).unwrap(elf); + assert(rela_info.type != none_reloc_type); // bug: `index` is in the free-list + } + const old_offset = elf.targetLoad(&relas[@intFromEnum(index)].offset); + elf.targetStore(&relas[@intFromEnum(index)].offset, @intCast( + old_offset - old_base + new_base, + )); + }, + } + } }; }; +/// Identifies a single entry in the GOT. +const GotKey = union(enum) { + /// The entry is a reserved word, initialized to zero. `initHeaders` will add as many of these + /// as the target machine ABI requires. + /// + /// This `u32` value exists to allow reserving multiple words with distinct keys. + reserved: u32, + + /// Value is the address of the given symbol. + symbol: Symbol.Id, + + /// Value is the signed offset of the given symbol from the TLS pointer. + tpoff: Symbol.Id, + + /// Value is the TLS module ID of the DSO we are creating. + /// + /// Used for the first of the two GOT entries generated by a TLSLD relocation. + tlsld0, + /// Value is always 0. + /// + /// Used for the second of the two GOT entries generated by a TLSLD relocation. + tlsld1, + + /// Value is the TLS module ID for the given STT_TLS symbol. + /// + /// Used for the first of the two GOT entries generated by a TLSGD relocation. + tlsgd0: Symbol.Id, + /// Value is the offset of the given STT_TLS symbol from the base of the per-module TLS area. + /// + /// Used for the second of the two GOT entries generated by a TLSGD relocation. + tlsgd1: Symbol.Id, +}; + +/// A relocation targeting a particular GOT entry. +const GotReloc = struct { + /// The node containing this relocation. Possible values are: + /// * An input section + /// * A section + /// * A NAV, UAV, or lazy code/data + /// * `.none`, if this relocation was deleted (in which case it should be ignored) + node: MappedFile.Node.Index, + /// The offset of the relocation inside of `node`. + offset: u64, + target: GotKey, + addend: i64, + type: GotReloc.Type, + + const deleted: GotReloc = .{ + .node = .none, + .offset = undefined, + .target = undefined, + .addend = undefined, + .type = undefined, + }; + + const Type = enum(u8) { + offset64, + offset32, + rel64, + rel32, + }; + + const Index = enum(u32) { + none = std.math.maxInt(u32), + _, + + fn get(index: GotReloc.Index, elf: *Elf) *GotReloc { + return &elf.got_relocs.items[@intFromEnum(index)]; + } + }; + + fn apply(reloc: *const GotReloc, elf: *Elf) void { + assert(elf.ehdrField(.type) != .REL); + if (reloc.node == .none) return; // deleted + if (reloc.node.hasMoved(&elf.mf) or elf.shndx.got.get(elf).ni.hasMoved(&elf.mf)) { + // There's no point applying the relocation now, because it will be re-applied by + // `flushMoved` at some point anyway. + return; + } + const node_vaddr: u64 = switch (elf.getNode(reloc.node)) { + .file => unreachable, + .ehdr => unreachable, + .shdr => unreachable, + .segment => unreachable, + .section => |shndx| shndx.vaddr(elf), + .input_section => |isi| isi.ptrConst(elf).vaddr, + inline .nav, + .uav, + .lazy_code, + .lazy_const_data, + => |i| Symbol.Id.local(i.symbol(elf)).value(elf), + }; + const dest_vaddr = node_vaddr + reloc.offset; + const dest_slice = reloc.node.slice(&elf.mf)[@intCast(reloc.offset)..]; + const target_endian = elf.targetEndian(); + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_index: u64 = elf.got.getIndex(reloc.target).?; + const got_offset: u64 = switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| @sizeOf(class.ElfN().Addr) * got_index, + }; + const addend: u64 = @bitCast(reloc.addend); + switch (reloc.type) { + .offset64 => std.mem.writeInt( + u64, + dest_slice[0..8], + got_offset +% addend, + target_endian, + ), + .offset32 => std.mem.writeInt( + u32, + dest_slice[0..4], + @intCast(got_offset +% addend), + target_endian, + ), + .rel64 => std.mem.writeInt( + i64, + dest_slice[0..8], + @bitCast(got_vaddr +% got_offset +% addend -% dest_vaddr), + target_endian, + ), + .rel32 => std.mem.writeInt( + i32, + dest_slice[0..4], + @intCast(@as(i64, @bitCast(got_vaddr +% got_offset +% addend -% dest_vaddr))), + target_endian, + ), + } + } +}; + +pub const MachineRelocType = union { + X86_64: std.elf.R_X86_64, + AARCH64: std.elf.R_AARCH64, + RISCV: std.elf.R_RISCV, + PPC64: std.elf.R_PPC64, + + pub fn none(elf: *Elf) MachineRelocType { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .AARCH64 => .{ .AARCH64 = .NONE }, + .PPC64 => .{ .PPC64 = .NONE }, + .RISCV => .{ .RISCV = .NONE }, + .X86_64 => .{ .X86_64 = .NONE }, + }; + } + pub fn jumpSlot(elf: *Elf) MachineRelocType { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .X86_64 => .{ .X86_64 = .JUMP_SLOT }, + }; + } + pub fn globDat(elf: *Elf) MachineRelocType { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .X86_64 => .{ .X86_64 = .GLOB_DAT }, + }; + } + pub fn dtpOffAddr(elf: *Elf) MachineRelocType { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .X86_64 => .{ .X86_64 = .DTPOFF64 }, + }; + } + pub fn absAddr(elf: *Elf) MachineRelocType { + 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) MachineRelocType { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + .X86_64 => .{ .X86_64 = .SIZE64 }, + }; + } + + pub fn wrap(int: u32, elf: *Elf) MachineRelocType { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + inline .AARCH64, + .PPC64, + .RISCV, + .X86_64, + => |machine| @unionInit(MachineRelocType, @tagName(machine), @enumFromInt(int)), + }; + } + pub fn unwrap(rt: MachineRelocType, elf: *Elf) u32 { + return switch (elf.ehdrField(.machine)) { + else => unreachable, + inline .AARCH64, + .PPC64, + .RISCV, + .X86_64, + => |machine| @intFromEnum(@field(rt, @tagName(machine))), + }; + } +}; + +/// A relocation targeting an arbitrary symbol with a fixed addend. +const SymbolReloc = struct { + /// The node containing this relocation. Possible values are: + /// * An input section + /// * A section + /// * A NAV, UAV, or lazy code/data + node: MappedFile.Node.Index, + /// The offset of the relocation inside of `node`. + offset: u64, + /// A symbol used to compute the relocated value. Precise meaning depends on `@"type"`. + target: Symbol.Id, + /// A signed constant used to compute the relocated value. Precise meaning depends on `@"type"`. + addend: i64, + /// Specifies how to apply the relocation. + type: SymbolReloc.Type, + /// Forms a linked list of all symbol relocations with the same `target`. This list exists so + /// that all relocations targeting a particular symbol can be re-applied if that symbol moves. + /// Doubly-linked so that relocations can be removed. + next: SymbolReloc.Index, + /// Back-reference in a doubly-linked list---see `next`. + prev: SymbolReloc.Index, + /// If this relocation has a corresponding output relocation, this is its index within the + /// appropriate SHT_RELA section (see `relaSection`). If there is no output relocation + /// corresponding to this relocation, this is `.none`. + /// + /// If we are producing a relocatable, this field is always populated, because all relocations + /// are emitted as output relocations. + /// + /// If we are producing a DSO, this field is populated if this relocation requires a runtime + /// relocation entry. The entry will be removed if we discover a definition which allows us to + /// statically resolve the relocation. + rela_index: Section.RelaIndex.Optional, + + /// Determines the section in which this relocation will be placed if it is outstanding. + /// + /// When producing a relocatable (ET_REL), the relocation section is `Section.rela.shndx` for + /// the section of `node`, and this function asserts that the aforementioned `rela.shndx` field + /// is populated. + /// + /// When producing a DSO, the relocation section is always `.rela.dyn`. It is not `.rela.plt` + /// because relocations in the GOTPLT are handled specially, without `SymbolReloc` entries. + fn relaSection(sr: *const SymbolReloc, elf: *Elf) Section.Index { + const shndx = switch (elf.ehdrField(.type)) { + .NONE, .CORE, _ => unreachable, + .REL => elf.getNodeShndx(sr.node).get(elf).rela.shndx, + .EXEC, .DYN => elf.shndx.rela_dyn, + }; + assert(shndx != .UNDEF); + return shndx; + } + + const Index = enum(u32) { + none = std.math.maxInt(u32), + _, + + fn get(index: SymbolReloc.Index, elf: *Elf) *SymbolReloc { + return &elf.symbol_relocs.items[@intFromEnum(index)]; + } + }; + + const Type = enum { + /// This input relocation is being directly forwarded to an `ElfN.Rela` entry in the output + /// file. `rela_index` is guaranteed to be populated. The ELF relocation type is available + /// in the `ElfN.Rela` entry. + /// + /// If we are emitting a relocatable (`ET_REL`), all symbol relocs use this type (since we + /// do not apply any relocations ourselves). Otherwise, no symbol relocs use this type. + write_rela, + + abs64, + abs32, + abs32s, + rel64, + rel32, + pltrel64, + pltrel32, + dtpoff64, + dtpoff32, + tpoff64, + tpoff32, + size64, + size32, + + fn dependsOnTlsSize(t: SymbolReloc.Type) bool { + return switch (t) { + .tpoff32, .tpoff64 => true, + else => false, + }; + } + }; + + fn apply(reloc: *const SymbolReloc, elf: *Elf) void { + assert(elf.ehdrField(.type) != .REL); + assert(reloc.node != .none); + if (reloc.node.hasMoved(&elf.mf) or reloc.target.hasMoved(elf)) { + // There's no point applying the relocation now, because it will be re-applied by + // `flushMoved` at some point anyway. + return; + } + if (reloc.rela_index != .none) { + // This relocation has been lowered to a runtime relocation. Until that changes, it is + // not our job to apply it. + return; + } + const node_vaddr: u64 = switch (elf.getNode(reloc.node)) { + .file => unreachable, + .ehdr => unreachable, + .shdr => unreachable, + .segment => unreachable, + .section => |shndx| shndx.vaddr(elf), + .input_section => |isi| isi.ptrConst(elf).vaddr, + inline .nav, + .uav, + .lazy_code, + .lazy_const_data, + => |i| Symbol.Id.local(i.symbol(elf)).value(elf), + }; + const dest_vaddr = node_vaddr + reloc.offset; + const dest_slice = reloc.node.slice(&elf.mf)[@intCast(reloc.offset)..]; + const target_endian = elf.targetEndian(); + const sym_value: u64, const sym_size: u64 = switch (elf.symPtr(reloc.target.index(elf))) { + inline else => |target_sym| .{ + elf.targetLoad(&target_sym.value), + elf.targetLoad(&target_sym.size), + }, + }; + const target_value = sym_value +% @as(u64, @bitCast(reloc.addend)); + type: switch (reloc.type) { + .write_rela => unreachable, + .abs64 => std.mem.writeInt( + u64, + dest_slice[0..8], + target_value, + target_endian, + ), + .abs32 => std.mem.writeInt( + u32, + dest_slice[0..4], + @intCast(target_value), + target_endian, + ), + .abs32s => std.mem.writeInt( + i32, + dest_slice[0..4], + @intCast(@as(i64, @bitCast(target_value))), + target_endian, + ), + .rel64 => std.mem.writeInt( + i64, + dest_slice[0..8], + @bitCast(target_value -% dest_vaddr), + target_endian, + ), + .rel32 => std.mem.writeInt( + i32, + dest_slice[0..4], + @intCast(@as(i64, @bitCast(target_value -% dest_vaddr))), + target_endian, + ), + .pltrel64 => { + const plt_index = elf.plt.getIndex(reloc.target) orelse continue :type .rel64; + if (elf.pltEntryIsDead(plt_index)) continue :type .rel64; + const plt_shndx: Section.Index, const plt_entry_size: u64 = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => .{ elf.shndx.plt_sec, 16 }, + }; + const plt_entry = plt_shndx.vaddr(elf) +% plt_index * plt_entry_size; + std.mem.writeInt( + i64, + dest_slice[0..8], + @bitCast(plt_entry +% @as(u64, @bitCast(reloc.addend)) -% dest_vaddr), + target_endian, + ); + }, + .pltrel32 => { + const plt_index = elf.plt.getIndex(reloc.target) orelse continue :type .rel32; + if (elf.pltEntryIsDead(plt_index)) continue :type .rel32; + const plt_shndx: Section.Index, const plt_entry_size: u64 = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => .{ elf.shndx.plt_sec, 16 }, + }; + const plt_entry = plt_shndx.vaddr(elf) +% plt_index * plt_entry_size; + std.mem.writeInt( + i32, + dest_slice[0..4], + @intCast(@as(i64, @bitCast( + plt_entry +% @as(u64, @bitCast(reloc.addend)) -% dest_vaddr, + ))), + target_endian, + ); + }, + .size64 => std.mem.writeInt( + u64, + dest_slice[0..8], + sym_size +% @as(u64, @bitCast(reloc.addend)), + target_endian, + ), + .size32 => std.mem.writeInt( + u32, + dest_slice[0..4], + @intCast(sym_size +% @as(u64, @bitCast(reloc.addend))), + target_endian, + ), + .dtpoff64 => std.mem.writeInt( + i64, + dest_slice[0..8], + @bitCast(target_value), + target_endian, + ), + .dtpoff32 => std.mem.writeInt( + i32, + dest_slice[0..4], + @intCast(@as(i64, @bitCast(target_value))), + target_endian, + ), + .tpoff64 => { + const tls_phndx = elf.getNode(elf.ni.tls).segment; + const tls_size: u64 = switch (elf.phdrSlice()) { + inline else => |phdr| tls_size: { + assert(elf.targetLoad(&phdr[tls_phndx].type) == .TLS); + break :tls_size elf.targetLoad(&phdr[tls_phndx].memsz); + }, + }; + std.mem.writeInt( + i64, + dest_slice[0..8], + @bitCast(target_value -% tls_size), + target_endian, + ); + }, + .tpoff32 => { + const tls_phndx = elf.getNode(elf.ni.tls).segment; + const tls_size: u64 = switch (elf.phdrSlice()) { + inline else => |phdr| tls_size: { + assert(elf.targetLoad(&phdr[tls_phndx].type) == .TLS); + break :tls_size elf.targetLoad(&phdr[tls_phndx].memsz); + }, + }; + std.mem.writeInt( + i32, + dest_slice[0..4], + @intCast(@as(i64, @bitCast(target_value -% tls_size))), + target_endian, + ); + }, + } + } + + fn delete(reloc: *SymbolReloc, elf: *Elf, index: SymbolReloc.Index) void { + assert(index.get(elf) == reloc); + switch (reloc.prev) { + .none => { + const target_ptr = reloc.target.index(elf).ptr(elf); + assert(target_ptr.first_target_reloc == index); + target_ptr.first_target_reloc = reloc.next; + }, + else => |prev| prev.get(elf).next = reloc.next, + } + switch (reloc.next) { + .none => {}, + else => |next| next.get(elf).prev = reloc.prev, + } + if (reloc.rela_index.unwrap()) |rela_index| { + reloc.relaSection(elf).relaDeleteOne(elf, rela_index); + if (elf.nodeRequiresTextrel(reloc.node)) { + elf.textrel_count -= 1; + } + } + if (reloc.type.dependsOnTlsSize()) { + assert(elf.tls_size_symbol_relocs.swapRemove(index)); + } + reloc.* = undefined; + } +}; + fn ensureUnusedSymbolCapacity(elf: *Elf, len: u32, kind: enum { all_local, maybe_global }) !void { const gpa = elf.base.comp.gpa; @@ -447,8 +1192,10 @@ fn ensureUnusedSymbolCapacity(elf: *Elf, len: u32, kind: enum { all_local, maybe fn ensureUnusedPltCapacity(elf: *Elf, len: u32) !void { const gpa = elf.base.comp.gpa; - try elf.got.plt.ensureUnusedCapacity(gpa, len); - const need_plt_capacity = elf.got.plt.count() + len; + try elf.shndx.rela_plt.relaEnsureAdditionalCapacity(elf, len); + + try elf.plt.ensureUnusedCapacity(gpa, len); + const need_plt_capacity = elf.plt.count() + len; switch (elf.ehdrField(.machine)) { else => |machine| @panic(@tagName(machine)), @@ -479,21 +1226,25 @@ fn ensureUnusedPltCapacity(elf: *Elf, len: u32) !void { const new_size = plt_sec_need_size +| plt_sec_need_size / MappedFile.growth_factor; try elf.shndx.plt_sec.get(elf).ni.resize(&elf.mf, gpa, new_size); } - - // Ensure the `.rela.plt` section's node is big enough - const rela_plt_shndx = elf.shndx.got_plt.get(elf).rela_shndx; - const rela_plt_need_size: usize = switch (elf.shdrPtr(rela_plt_shndx)) { - inline else => |shdr| @intCast(elf.targetLoad(&shdr.entsize) * need_plt_capacity), - }; - _, const rela_plt_cur_size = rela_plt_shndx.get(elf).ni.location(&elf.mf).resolve(&elf.mf); - if (rela_plt_cur_size < rela_plt_need_size) { - const new_size = rela_plt_need_size +| rela_plt_need_size / MappedFile.growth_factor; - try rela_plt_shndx.get(elf).ni.resize(&elf.mf, gpa, new_size); - } else { - // Still mark `.rela.plt` as resized so that the DT_PLTRELSZ entry can - // be updated if we do indeed add a PLT entry. - try rela_plt_shndx.get(elf).ni.resized(gpa, &elf.mf); - } + }, + } +} +/// Given an index into the PLT, returns whether that PLT entry is dead, meaning it may be reused at +/// any time and must not be targeted by relocations. See also the doc comment on `Elf.plt`. +fn pltEntryIsDead(elf: *Elf, plt_index: usize) bool { + assert(elf.shndx.plt != .UNDEF); + assert(plt_index <= elf.plt.count()); + // We track which PLT entries are alive based on the relocation entries, since there is a 1-1 + // mapping between PLT entries and `.rela.plt` entries and the relocation entries already have + // a free-list mechanism. + switch (elf.shdrPtr(elf.shndx.rela_plt)) { + inline else => |rela_shdr, class| { + const size = elf.targetLoad(&rela_shdr.size); + const relas: []class.ElfN().Rela = @ptrCast(@alignCast( + elf.shndx.rela_plt.get(elf).ni.slice(&elf.mf)[0..@intCast(size)], + )); + const rel_type = elf.targetLoad(&relas[plt_index].info).type; + return rel_type == MachineRelocType.none(elf).unwrap(elf); }, } } @@ -801,12 +1552,13 @@ fn addGlobalSymbolAssumeCapacity(elf: *Elf, opts: AddGlobalSymbolOptions) error{ if (new_global_ptr.dynsym_index != 0 and opts.visibility == .DEFAULT and opts.shndx == .UNDEF and - @"type" == .FUNC) + (@"type" == .FUNC or @"type" == std.elf.STT.GNU_IFUNC)) { // We're adding an undefined global STT_FUNC symbol which could be resolved by another DSO. - // We therefore might need a PLT entry, so let's add one now. TODO: it'd be good to remove - // the PLT entry if we later discover a link inpu which resolves this reference. + // We therefore might need a PLT entry, so let's add one now. elf.addPltEntry(opts.name.strtab, new_global_ptr.dynsym_index); + // TODO: we also need to emit a PLT entry if the symbol could be preempted/interposed! By + // not doing that we're basically implementing the behavior of `-Bsymbolic-functions`. } return .global(opts.name.strtab); @@ -823,6 +1575,7 @@ fn setGlobalSymbolValue( shndx: Section.Index, }, ) void { + assert(new.shndx != .UNDEF); const old_node = global_ptr.symtab_index.ptr(elf).node; if (old_node != .none) { if (global_ptr.next_in_node != .empty) { @@ -897,7 +1650,40 @@ fn setGlobalSymbolValue( }, }; - global_ptr.flushMoved(elf, new.value); + // If this symbol was previously undefined, it may have had a PLT entry. If so, we now need to + // delete its newly-unnecessary runtime relocation to avoid a runtime dynamic linker error. + // This also allows the PLT entry to be reused---see `pltEntryIsDead`. + if (elf.plt.getIndex(.global(global_name))) |plt_index| { + // TODO: we might still need the PLT entry if the symbol could be preempted/interposed! See + // matching comment at the end of `addGlobalSymbolAssumeCapacity`. + if (!elf.pltEntryIsDead(plt_index)) { + elf.shndx.rela_plt.relaDeleteOne(elf, @enumFromInt(plt_index)); + assert(elf.pltEntryIsDead(plt_index)); + } + } + + // If this symbol was previously undefined, relocations targeting it may have been lowered to + // runtime relocations which we have now discovered we do not need, so delete those. + if (elf.shndx.dynamic != .UNDEF) { + var ri = global_ptr.symtab_index.ptr(elf).first_target_reloc; + while (ri != .none) { + const reloc = ri.get(elf); + assert(reloc.target == Symbol.Id.global(global_name)); + if (reloc.rela_index.unwrap()) |rela_index| { + reloc.relaSection(elf).relaDeleteOne(elf, rela_index); + if (elf.nodeRequiresTextrel(reloc.node)) { + elf.textrel_count -= 1; + } + reloc.rela_index = .none; + } + ri = reloc.next; + } + } + + // Finally, update the symbol value, re-applying target relocations. Also note that because we + // possibly removed the PLT entry above, some relocations which were previously targeting the + // PLT will now instead target the symbol itself. + Symbol.Id.global(global_name).flushMoved(elf, new.value); } /// When the same global symbol appears in two inputs---even if one symbol is defined and the other /// undefined---their visibility values are combined to determine the resulting visibility, which @@ -1032,14 +1818,45 @@ fn moveDemotedGlobal(elf: *Elf, global_ptr: *Symbol.Global) void { if (elf.targetEndian() != native_endian) { std.mem.byteSwapAllFields(class.ElfN().Sym, dynsym); } + global_ptr.dynsym_index = 0; } }, } } fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void { const target_endian = elf.targetEndian(); - const plt_index: u32 = @intCast(elf.got.plt.count()); - elf.got.plt.putAssumeCapacityNoClobber(.global(global_name), {}); + + // We use the existing free-list tracking of the `.rela.plt` section to also behave as a + // free-list for the PLT itself---see `pltEntryIsDead` for details. + const plt_index: u32 = @intFromEnum(elf.shndx.rela_plt.relaAddOneAssumeCapacity(elf, .{ + .type = .jumpSlot(elf), + .offset = 0, // populated later + .raw_sym_index = dynsym_index, + .addend = 0, + })); + + // Now that we know the index, we can set the relocation's offset. + const got_plt_addr = switch (elf.shdrPtr(elf.shndx.got_plt)) { + inline else => |shdr, class| got_plt_addr: { + const ent_size = @sizeOf(class.ElfN().Addr); + assert(elf.targetLoad(&shdr.entsize) == ent_size); + const offset = ent_size * @as(u64, 3 + plt_index); + assert(offset <= elf.targetLoad(&shdr.size)); + break :got_plt_addr elf.targetLoad(&shdr.addr) + offset; + }, + }; + elf.shndx.rela_plt.relaSetOffset(elf, @enumFromInt(plt_index), got_plt_addr); + + if (plt_index < elf.plt.count()) { + // We reused a free entry, so we're already done! + elf.plt.setKey(plt_index, .global(global_name)); + return; + } + + // We added a new entry, so we now need to extend the PLT sections. + assert(plt_index == elf.plt.count()); + elf.plt.putAssumeCapacityNoClobber(.global(global_name), {}); + switch (elf.ehdrField(.machine)) { else => |machine| @panic(@tagName(machine)), .X86_64 => { @@ -1067,12 +1884,12 @@ fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void }, }; - const got_plt_shndx = elf.shndx.got_plt; const got_plt_ni = elf.shndx.got_plt.get(elf).ni; - const got_plt_addr = got_plt_addr: switch (elf.shdrPtr(got_plt_shndx)) { + switch (elf.shdrPtr(elf.shndx.got_plt)) { inline else => |shdr, class| { const ent_size = @sizeOf(class.ElfN().Addr); const old_size = ent_size * (3 + plt_index); + assert(elf.targetLoad(&shdr.size) == old_size); elf.targetStore(&shdr.size, old_size + ent_size); std.mem.writeInt( class.ElfN().Addr, @@ -1080,9 +1897,8 @@ fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void @intCast(plt_addr), target_endian, ); - break :got_plt_addr elf.targetLoad(&shdr.addr) + old_size; }, - }; + } const plt_sec_ni = elf.shndx.plt_sec.get(elf).ni; switch (elf.shdrPtr(elf.shndx.plt_sec)) { @@ -1105,30 +1921,6 @@ fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void ); }, } - - const rela_plt_shndx = got_plt_shndx.get(elf).rela_shndx; - const rela_plt_ni = rela_plt_shndx.get(elf).ni; - switch (elf.shdrPtr(rela_plt_shndx)) { - inline else => |shdr, class| { - const Rela = class.ElfN().Rela; - const rela_size = elf.targetLoad(&shdr.entsize); - const old_size = rela_size * plt_index; - const new_size = old_size + rela_size; - elf.targetStore(&shdr.size, new_size); - const rela: *Rela = @ptrCast(@alignCast( - rela_plt_ni.slice(&elf.mf)[@intCast(old_size)..@intCast(new_size)], - )); - rela.* = .{ - .offset = @intCast(got_plt_addr), - .info = .{ - .type = @intFromEnum(std.elf.R_X86_64.JUMP_SLOT), - .sym = @intCast(dynsym_index), - }, - .addend = 0, - }; - if (target_endian != native_endian) std.mem.byteSwapAllFields(Rela, rela); - }, - } }, } } @@ -1142,7 +1934,7 @@ const Symbol = struct { node: MappedFile.Node.Index, /// The head of a linked list of relocations targeting this symbol. - first_target_reloc: Reloc.Index, + first_target_reloc: SymbolReloc.Index, const Global = struct { /// The current index of the symtab entry for this global symbol. @@ -1159,16 +1951,6 @@ const Symbol = struct { /// /// If `node` is `.none`, this is `.empty`. prev_in_node: String(.strtab), - - /// Like `Symbol.Index.flushMoved`, but also updates the dynamic symbol table if necessary. - fn flushMoved(g: *const Global, elf: *Elf, value: u64) void { - g.symtab_index.flushMoved(elf, value); - if (g.dynsym_index != 0) { - switch (elf.dynsymPtr(g.dynsym_index)) { - inline else => |sym| elf.targetStore(&sym.value, @intCast(value)), - } - } - } }; /// An index directly into the symtab. These values are not stable (global symbols are sometimes @@ -1181,23 +1963,20 @@ const Symbol = struct { null = 0, _, - fn flushMoved(si: Symbol.Index, elf: *Elf, value: u64) void { - switch (elf.symPtr(si)) { - inline else => |sym| elf.targetStore(&sym.value, @intCast(value)), - } - if (elf.ehdrField(.type) != .REL) { - var ri = si.ptr(elf).first_target_reloc; - while (ri != .none) { - const reloc = ri.get(elf); - assert(reloc.target.index(elf) == si); - reloc.apply(elf); - ri = reloc.next; - } - } - } fn ptr(si: Symbol.Index, elf: *Elf) *Symbol { return &elf.symtab.items[@intFromEnum(si)]; } + + fn applyTargetRelocs(si: Symbol.Index, elf: *Elf) void { + assert(elf.ehdrField(.type) != .REL); + var ri = si.ptr(elf).first_target_reloc; + while (ri != .none) { + const reloc = ri.get(elf); + assert(reloc.target.index(elf) == si); + reloc.apply(elf); + ri = reloc.next; + } + } }; /// A `LocalIndex` is a raw index into the symtab like `Index`, but it guarantees that the @@ -1262,6 +2041,44 @@ const Symbol = struct { }; } + fn flushMoved(sym_id: Symbol.Id, elf: *Elf, new_value: u64) void { + // Update the symbol value in `.symtab` + const sym_index = sym_id.index(elf); + switch (elf.symPtr(sym_index)) { + inline else => |sym| elf.targetStore(&sym.value, @intCast(new_value)), + } + + // Update the symbol value in `.dynsym` if applicable + switch (sym_id.unwrap()) { + .local => {}, + .global => |name| { + const g = elf.globalByName(name).?; + if (g.dynsym_index != 0) { + switch (elf.dynsymPtr(g.dynsym_index)) { + inline else => |sym| elf.targetStore(&sym.value, @intCast(new_value)), + } + } + }, + } + + // Re-apply relocations targeting this symbol + if (elf.ehdrField(.type) != .REL) { + sym_index.applyTargetRelocs(elf); + } + + // Update GOT entries targeting this symbol + if (elf.got.getIndex(.{ .symbol = sym_id })) |got_index| { + elf.updateGotEntry(got_index); + } + if (elf.got.getIndex(.{ .tpoff = sym_id })) |got_index| { + elf.updateGotEntry(got_index); + } + if (elf.got.getIndex(.{ .tlsgd0 = sym_id })) |got_index| { + elf.updateGotEntry(got_index); + elf.updateGotEntry(got_index + 1); // tlsgd1 + } + } + /// Returns `true` if the target of `s` has moved, meaning the symbol's value will change at /// some point due to a call to `flushMoved`. fn hasMoved(s: Symbol.Id, elf: *Elf) bool { @@ -1328,7 +2145,8 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !link.File.SymbolId { .type = sym_type, .shndx = shndx, }), - .first_reloc = .none, + .first_symbol_reloc = .none, + .first_got_reloc = .none, }; elf.nodes.appendAssumeCapacity(switch (lazy.kind) { .code => .{ .lazy_code = @enumFromInt(gop.index) }, @@ -1377,7 +2195,7 @@ pub fn addReloc( offset: u64, target: link.File.SymbolId, addend: i64, - @"type": Reloc.Type, + @"type": MachineRelocType, ) !void { const node: MappedFile.Node.Index = Node.fromAtom(atom); try elf.ensureUnusedRelocCapacity(node, 1); @@ -1532,7 +2350,6 @@ const StringTable = struct { .{ .slice = slice_const }, ); if (gop.found_existing) return gop.key_ptr.*; - try ni.resized(gpa, &elf.mf); const old_size, const new_size = size: switch (elf.shdrPtr(shndx)) { inline else => |shdr| { const old_size: u32 = @intCast(elf.targetLoad(&shdr.size)); @@ -1541,6 +2358,9 @@ const StringTable = struct { break :size .{ old_size, new_size }; }, }; + if (shndx == elf.shndx.dynstr) { + elf.updateDynamicEntry(std.elf.DT_STRSZ, new_size); + } _, const node_size = ni.location(&elf.mf).resolve(&elf.mf); if (new_size > node_size) try ni.resize(&elf.mf, gpa, new_size +| new_size / MappedFile.growth_factor); @@ -1569,274 +2389,6 @@ const GotIndex = enum(u32) { } }; -const Reloc = extern struct { - type: Reloc.Type, - prev: Reloc.Index, - next: Reloc.Index, - node: MappedFile.Node.Index, - target: Symbol.Id, - index: Section.RelIndex, - offset: u64, - addend: i64, - - pub const Type = extern union { - X86_64: std.elf.R_X86_64, - AARCH64: std.elf.R_AARCH64, - RISCV: std.elf.R_RISCV, - PPC64: std.elf.R_PPC64, - - pub fn none(elf: *Elf) Reloc.Type { - return switch (elf.ehdrField(.machine)) { - else => unreachable, - .AARCH64 => .{ .AARCH64 = .NONE }, - .PPC64 => .{ .PPC64 = .NONE }, - .RISCV => .{ .RISCV = .NONE }, - .X86_64 => .{ .X86_64 = .NONE }, - }; - } - 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 fn wrap(int: u32, elf: *Elf) Reloc.Type { - return switch (elf.ehdrField(.machine)) { - else => unreachable, - inline .AARCH64, - .PPC64, - .RISCV, - .X86_64, - => |machine| @unionInit(Reloc.Type, @tagName(machine), @enumFromInt(int)), - }; - } - pub fn unwrap(rt: Reloc.Type, elf: *Elf) u32 { - return switch (elf.ehdrField(.machine)) { - else => unreachable, - inline .AARCH64, - .PPC64, - .RISCV, - .X86_64, - => |machine| @intFromEnum(@field(rt, @tagName(machine))), - }; - } - }; - - pub const Index = enum(u32) { - none = std.math.maxInt(u32), - _, - - pub fn get(si: Reloc.Index, elf: *Elf) *Reloc { - return &elf.relocs.items[@intFromEnum(si)]; - } - }; - - pub fn apply(reloc: *const Reloc, elf: *Elf) void { - assert(elf.ehdrField(.type) != .REL); - assert(reloc.node != .none); - if (reloc.node.hasMoved(&elf.mf) or reloc.target.hasMoved(elf)) { - // There's no point applying the relocation now, because it will be re-applied by - // `flushMoved` at some point anyway. - return; - } - const node_vaddr: u64 = switch (elf.getNode(reloc.node)) { - .file => unreachable, - .ehdr => unreachable, - .shdr => unreachable, - .segment => unreachable, - .section => |shndx| shndx.vaddr(elf), - .input_section => |isi| isi.ptrConst(elf).vaddr, - inline .nav, - .uav, - .lazy_code, - .lazy_const_data, - => |i| Symbol.Id.local(i.symbol(elf)).value(elf), - }; - const dest_vaddr = node_vaddr + reloc.offset; - const dest_slice = reloc.node.slice(&elf.mf)[@intCast(reloc.offset)..]; - const target_endian = elf.targetEndian(); - switch (elf.symPtr(reloc.target.index(elf))) { - inline else => |target_sym, class| { - const target_value = elf.targetLoad(&target_sym.value) +% @as(u64, @bitCast(reloc.addend)); - switch (elf.ehdrField(.machine)) { - else => |machine| @panic(@tagName(machine)), - .X86_64 => switch (reloc.type.X86_64) { - else => |kind| @panic(@tagName(kind)), - .@"64" => std.mem.writeInt( - u64, - dest_slice[0..8], - target_value, - target_endian, - ), - .PC32 => std.mem.writeInt( - i32, - dest_slice[0..4], - @intCast(@as(i64, @bitCast(target_value -% dest_vaddr))), - target_endian, - ), - .PLT32 => std.mem.writeInt( - i32, - dest_slice[0..4], - @intCast(@as(i64, @bitCast(if (elf.got.plt.getIndex(reloc.target)) |plt_index| - elf.targetLoad(&@field( - elf.shdrPtr(elf.shndx.plt_sec), - @tagName(class), - ).addr) +% 16 * plt_index +% - @as(u64, @bitCast(reloc.addend)) -% dest_vaddr - else - target_value -% dest_vaddr))), - target_endian, - ), - .@"32" => std.mem.writeInt( - u32, - dest_slice[0..4], - @intCast(target_value), - target_endian, - ), - .@"32S" => std.mem.writeInt( - i32, - dest_slice[0..4], - @intCast(@as(i64, @bitCast(target_value))), - target_endian, - ), - .TLSLD => std.mem.writeInt( - i32, - dest_slice[0..4], - @intCast(@as(i64, @bitCast( - elf.shndx.got.vaddr(elf) +% - @as(u64, @bitCast(reloc.addend)) +% - @as(u64, 8) * elf.got.tlsld.unwrap().? -% - dest_vaddr, - ))), - target_endian, - ), - .DTPOFF32 => std.mem.writeInt( - i32, - dest_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.ni.tls).segment]; - assert(elf.targetLoad(&ph.type) == .TLS); - std.mem.writeInt( - i32, - dest_slice[0..4], - @intCast(@as(i64, @bitCast(target_value -% elf.targetLoad(&ph.memsz)))), - target_endian, - ); - }, - .SIZE32 => std.mem.writeInt( - u32, - dest_slice[0..4], - @intCast( - elf.targetLoad(&target_sym.size) +% @as(u64, @bitCast(reloc.addend)), - ), - target_endian, - ), - .SIZE64 => std.mem.writeInt( - u64, - dest_slice[0..8], - elf.targetLoad(&target_sym.size) +% @as(u64, @bitCast(reloc.addend)), - target_endian, - ), - }, - } - }, - } - } - - pub fn delete(reloc: *Reloc, elf: *Elf) void { - switch (reloc.prev) { - .none => { - const target_ptr = reloc.target.index(elf).ptr(elf); - assert(target_ptr.first_target_reloc.get(elf) == reloc); - target_ptr.first_target_reloc = reloc.next; - }, - else => |prev| prev.get(elf).next = reloc.next, - } - switch (reloc.next) { - .none => {}, - else => |next| next.get(elf).prev = reloc.prev, - } - switch (elf.ehdrField(.type)) { - .NONE, .CORE, _ => unreachable, - .REL => { - const sh = elf.getNodeShndx(reloc.node).get(elf); - switch (elf.shdrPtr(sh.rela_shndx)) { - inline else => |shdr, class| { - const Rela = class.ElfN().Rela; - const ent_size = elf.targetLoad(&shdr.entsize); - const start = ent_size * reloc.index.unwrap().?; - const rela_slice = sh.rela_shndx.get(elf).ni.slice(&elf.mf); - const rela: *Rela = @ptrCast(@alignCast( - rela_slice[@intCast(start)..][0..@intCast(ent_size)], - )); - rela.* = .{ - .offset = @intFromEnum(sh.rela_free), - .info = .{ - .type = @intCast(Reloc.Type.none(elf).unwrap(elf)), - .sym = 0, - }, - .addend = 0, - }; - }, - } - sh.rela_free = reloc.index; - }, - .EXEC, .DYN => assert(reloc.index == .none), - } - reloc.* = undefined; - } - - fn updateTargetIndex(reloc: *const Reloc, elf: *Elf) void { - assert(elf.ehdrField(.type) == .REL); - const sh = elf.getNodeShndx(reloc.node).get(elf); - switch (elf.shdrPtr(sh.rela_shndx)) { - inline else => |shdr, class| { - assert(elf.targetLoad(&shdr.entsize) == @sizeOf(class.ElfN().Rela)); - const size = elf.targetLoad(&shdr.size); - const raw_rela_slice = sh.rela_shndx.get(elf).ni.slice(&elf.mf); - const rela_slice: []class.ElfN().Rela = @ptrCast(@alignCast(raw_rela_slice[0..@intCast(size)])); - elf.targetStore(&rela_slice[reloc.index.unwrap().?].info, .{ - .type = @intCast(reloc.type.unwrap(elf)), - .sym = @intCast(@intFromEnum(reloc.target.index(elf))), - }); - }, - } - } - - fn updateNodeOffset(reloc: *const Reloc, elf: *Elf, node_offset: u64) void { - assert(elf.ehdrField(.type) == .REL); - const total_offset = node_offset + reloc.offset; - const sh = elf.getNodeShndx(reloc.node).get(elf); - switch (elf.shdrPtr(sh.rela_shndx)) { - inline else => |shdr, class| { - assert(elf.targetLoad(&shdr.entsize) == @sizeOf(class.ElfN().Rela)); - const size = elf.targetLoad(&shdr.size); - const raw_rela_slice = sh.rela_shndx.get(elf).ni.slice(&elf.mf); - const rela_slice: []class.ElfN().Rela = @ptrCast(@alignCast(raw_rela_slice[0..@intCast(size)])); - elf.targetStore(&rela_slice[reloc.index.unwrap().?].offset, @intCast(total_offset)); - }, - } - } - - comptime { - if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Reloc) == 40); - } -}; - pub fn open( arena: std.mem.Allocator, comp: *Compilation, @@ -1941,6 +2493,8 @@ fn create( .dynstr = .UNDEF, .dynamic = .UNDEF, .tdata = .UNDEF, + .rela_dyn = .UNDEF, + .rela_plt = .UNDEF, .init_array = .UNDEF, .fini_array = .UNDEF, .preinit_array = .UNDEF, @@ -1957,13 +2511,10 @@ fn create( .shstrtab = .{ .map = .empty }, .strtab = .{ .map = .empty }, .dynstr = .{ .map = .empty }, - .got = .{ - .len = 0, - .tlsld = .none, - .plt = .empty, - }, - .first_plt_reloc = .none, - .first_dynamic_reloc = .none, + .got = .empty, + .plt = .empty, + .plt_first_symbol_reloc = .none, + .dynamic_first_symbol_reloc = .none, .needed = .empty, .inputs = .empty, .input_sections = .empty, @@ -1975,12 +2526,15 @@ fn create( .pending_index = 0, }), .pending_uavs = .empty, - .relocs = .empty, + .symbol_relocs = .empty, + .got_relocs = .empty, + .tls_size_symbol_relocs = .empty, .section_by_name = .empty, .changed_symtab_index = .empty, .const_prog_node = .none, .synth_prog_node = .none, .input_prog_node = .none, + .textrel_count = 0, }; errdefer elf.deinit(); @@ -2004,7 +2558,8 @@ pub fn deinit(elf: *Elf) void { elf.shstrtab.map.deinit(gpa); elf.strtab.map.deinit(gpa); elf.dynstr.map.deinit(gpa); - elf.got.plt.deinit(gpa); + elf.got.deinit(gpa); + elf.plt.deinit(gpa); elf.needed.deinit(gpa); for (elf.inputs.items) |input| if (input.member) |m| gpa.free(m); elf.inputs.deinit(gpa); @@ -2013,7 +2568,9 @@ pub fn deinit(elf: *Elf) void { elf.uavs.deinit(gpa); for (&elf.lazy.values) |*lazy| lazy.map.deinit(gpa); elf.pending_uavs.deinit(gpa); - elf.relocs.deinit(gpa); + elf.symbol_relocs.deinit(gpa); + elf.got_relocs.deinit(gpa); + elf.tls_size_symbol_relocs.deinit(gpa); elf.section_by_name.deinit(gpa); elf.changed_symtab_index.deinit(gpa); elf.* = undefined; @@ -2323,7 +2880,7 @@ fn initHeaders( .entsize = 0, }; if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Shdr, sh_undef); - elf.shdrs.appendAssumeCapacity(.{ .lsi = .null, .ni = .none, .rela_shndx = .UNDEF, .rela_free = .none }); + elf.shdrs.appendAssumeCapacity(.{ .lsi = .null, .ni = .none, .rela = .{ .shndx = .UNDEF } }); elf.symtab.addOneAssumeCapacity().* = .{ .node = .none, @@ -2398,8 +2955,15 @@ fn initHeaders( if (@"type" != .REL) { elf.shndx.got = try elf.addSection(elf.ni.data_rel_ro, .{ .name = ".got", + .type = .PROGBITS, + // Reserve space for the reserved words, populated later. + .size = switch (machine) { + else => @panic(@tagName(machine)), + .X86_64 => 3 * 8, + }, .flags = .{ .WRITE = true, .ALLOC = true }, .addralign = addr_align, + .entsize = @intCast(addr_align.toByteUnits()), }); elf.shndx.got_plt = try elf.addSection( if (elf.options.z_now) elf.ni.data_rel_ro else elf.ni.data, @@ -2413,6 +2977,7 @@ fn initHeaders( .X86_64 => 3 * 8, }, .addralign = addr_align, + .entsize = @intCast(addr_align.toByteUnits()), }, ); const plt_size: std.elf.Xword, const plt_align: std.mem.Alignment, const plt_sec = @@ -2508,7 +3073,7 @@ fn initHeaders( .NONE, _ => unreachable, inline else => |ct_class| @sizeOf(ct_class.ElfN().Rela), }; - elf.shndx.got.get(elf).rela_shndx = try elf.addSection(elf.ni.rodata, .{ + elf.shndx.rela_dyn = try elf.addSection(elf.ni.rodata, .{ .name = ".rela.dyn", .type = .RELA, .flags = .{ .ALLOC = true }, @@ -2517,13 +3082,12 @@ fn initHeaders( .entsize = rela_size, .node_align = elf.mf.flags.block_size, }); - const got_plt_shndx = elf.shndx.got_plt; - got_plt_shndx.get(elf).rela_shndx = try elf.addSection(elf.ni.rodata, .{ + elf.shndx.rela_plt = try elf.addSection(elf.ni.rodata, .{ .name = ".rela.plt", .type = .RELA, .flags = .{ .ALLOC = true, .INFO_LINK = true }, .link = elf.shndx.dynsym.toSection().?, - .info = got_plt_shndx.toSection().?, + .info = elf.shndx.got_plt.toSection().?, .addralign = addr_align, .entsize = rela_size, .node_align = elf.mf.flags.block_size, @@ -2546,7 +3110,7 @@ fn initHeaders( 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp *0x0(%rip) 0x0f, 0x1f, 0x40, 0x00, // nopl 0x0(%rax) }); - elf.first_plt_reloc = @enumFromInt(elf.relocs.items.len); + elf.plt_first_symbol_reloc = @enumFromInt(elf.symbol_relocs.items.len); try elf.ensureUnusedRelocCapacity(plt_ni, 2); elf.addRelocAssumeCapacity( plt_ni, @@ -2575,6 +3139,26 @@ fn initHeaders( elf.phdrs.items[tls_phndx] = elf.ni.tls; } + // Populate reserved GOT words. + switch (machine) { + else => @panic(@tagName(machine)), + .X86_64 => { + try elf.got.ensureUnusedCapacity(gpa, 3); + elf.got.putAssumeCapacityNoClobber(switch (have_dynamic_section) { + true => .{ .symbol = .local(elf.shndx.dynamic.get(elf).lsi) }, + false => .{ .reserved = 0 }, + }, .none); + elf.got.putAssumeCapacityNoClobber(.{ .reserved = 1 }, .none); + elf.got.putAssumeCapacityNoClobber(.{ .reserved = 2 }, .none); + }, + } + switch (elf.shdrPtr(elf.shndx.got)) { + inline else => |shdr, ct_class| { + const Addr = ct_class.ElfN().Addr; + assert(elf.targetLoad(&shdr.size) == elf.got.count() * @sizeOf(Addr)); + }, + } + // Create any always-provided linker-defined symbols. The symbols marking the `INIT_ARRAY`/ // `FINI_ARRAY`/`PREINIT_ARRAY` sections are instead created by `createInitFiniArraySection` // when needed (it seems to be legal to leave those undefined if the section doesn't exist). @@ -2679,7 +3263,7 @@ fn getNode(elf: *const Elf, ni: MappedFile.Node.Index) Node { return elf.nodes.get(@intFromEnum(ni)); } /// Asserts that `ni` is a section, input section, NAV, UAV, or lazy code/data. -fn getNodeShndx(elf: *Elf, ni: MappedFile.Node.Index) Section.Index { +fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index { return switch (elf.getNode(ni)) { .file => unreachable, .ehdr => unreachable, @@ -2717,55 +3301,80 @@ fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { /// Asserts that `ni` must be a node which supports relocations (see `Elf.Node`). Does not support /// the special-case sections '.plt' and '.dynamic'. fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void { - const first_reloc_ptr: *Reloc.Index = switch (elf.getNode(ni)) { + const symbol_relocs: *SymbolReloc.Index, const got_relocs: ?*GotReloc.Index = switch (elf.getNode(ni)) { .file => unreachable, // cannot contain relocs .ehdr => unreachable, // cannot contain relocs .shdr => unreachable, // cannot contain relocs .segment => unreachable, // cannot contain relocs .section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported) - .input_section => |isi| &elf.input_sections.items[@intFromEnum(isi)].first_reloc, - .nav => |nmi| &elf.navs.values()[@intFromEnum(nmi)].first_reloc, - .uav => |umi| &elf.uavs.values()[@intFromEnum(umi)].first_reloc, - inline .lazy_code, .lazy_const_data => |lmi| &elf.lazy.getPtr(lmi.ref().kind).map.values()[lmi.ref().index].first_reloc, + .input_section => |isi| .{ + &elf.input_sections.items[@intFromEnum(isi)].first_symbol_reloc, + &elf.input_sections.items[@intFromEnum(isi)].first_got_reloc, + }, + .nav => |nmi| .{ + &elf.navs.values()[@intFromEnum(nmi)].first_symbol_reloc, + &elf.navs.values()[@intFromEnum(nmi)].first_got_reloc, + }, + .uav => |umi| .{ + &elf.uavs.values()[@intFromEnum(umi)].first_symbol_reloc, + null, + }, + inline .lazy_code, .lazy_const_data => |lmi| .{ + &elf.lazy.getPtr(lmi.ref().kind).map.values()[lmi.ref().index].first_symbol_reloc, + &elf.lazy.getPtr(lmi.ref().kind).map.values()[lmi.ref().index].first_got_reloc, + }, }; - if (first_reloc_ptr.* != .none) { - for (elf.relocs.items[@intFromEnum(first_reloc_ptr.*)..]) |*reloc| { + + if (symbol_relocs.* != .none) { + for ( + elf.symbol_relocs.items[@intFromEnum(symbol_relocs.*)..], + @intFromEnum(symbol_relocs.*).., + ) |*reloc, index| { if (reloc.node != ni) break; - reloc.delete(elf); + reloc.delete(elf, @enumFromInt(index)); } } - first_reloc_ptr.* = @enumFromInt(elf.relocs.items.len); + symbol_relocs.* = @enumFromInt(elf.symbol_relocs.items.len); + + if (got_relocs) |ptr| { + if (ptr.* != .none) { + for (elf.got_relocs.items[@intFromEnum(ptr.*)..]) |*reloc| { + if (reloc.node != ni) break; + reloc.* = .deleted; + } + } + ptr.* = @enumFromInt(elf.got_relocs.items.len); + } } -/// Given that `node` has moved, updates all relocations in `node` (starting from `first_reloc`) as -/// needed. In relocatables, this means updating the offsets of those relocations. In ELF modules, -/// this means applying the relocations. +/// Given that `node` has moved, updates all relocations in `node` as needed. In relocatables, this +/// means updating the relocations' offsets. In ELF modules, this means applying the relocations. fn flushMovedNodeRelocs( elf: *Elf, node: MappedFile.Node.Index, node_vaddr: u64, - first_reloc: Reloc.Index, + first_symbol_reloc: SymbolReloc.Index, + first_got_reloc: GotReloc.Index, ) void { - if (first_reloc == .none) return; - switch (elf.ehdrField(.type)) { - .NONE, .CORE, _ => unreachable, - .REL => { - // In a relocatable, we're not actually applying any relocations ourselves, but we need - // to update the offsets of the relocation entries since the node they're in has moved. - for (elf.relocs.items[@intFromEnum(first_reloc)..]) |*reloc| { - if (reloc.node != node) break; - reloc.updateNodeOffset(elf, node_vaddr); - } - }, - .EXEC, .DYN => { - // For an ELF module, we just need to apply relocations. - for (elf.relocs.items[@intFromEnum(first_reloc)..]) |*reloc| { - if (reloc.node != node) break; + if (first_symbol_reloc != .none) { + for (elf.symbol_relocs.items[@intFromEnum(first_symbol_reloc)..]) |*reloc| { + if (reloc.node != node) break; + if (reloc.rela_index.unwrap()) |rela_index| { + // Update the offsets of any `ElfN.Rela` entry we've emitted, since the node they're + // in has moved, so their offset within the section might also have moved. + reloc.relaSection(elf).relaSetOffset(elf, rela_index, node_vaddr + reloc.offset); + } else { + // We've applied this relocation ourselves! Just re-apply it now. reloc.apply(elf); } - // TODO: once we're emitting runtime relocation entries, we need to update their offsets - // too, like the logic for relocatables above. - }, + } + } + + if (first_got_reloc != .none) { + for (elf.got_relocs.items[@intFromEnum(first_got_reloc)..]) |*reloc| { + if (reloc.node != node) break; + reloc.apply(elf); + } } } @@ -3109,7 +3718,8 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavM .type = elf.navType(nav.resolved.?), .shndx = shndx, }), - .first_reloc = .none, + .first_symbol_reloc = .none, + .first_got_reloc = .none, }; elf.nodes.appendAssumeCapacity(.{ .nav = nmi }); } @@ -3158,7 +3768,7 @@ fn uavMapIndex( .type = .OBJECT, .shndx = shndx, }), - .first_reloc = .none, + .first_symbol_reloc = .none, }; elf.nodes.appendAssumeCapacity(.{ .uav = umi }); elf.const_prog_node.increaseEstimatedTotalItems(1); @@ -3447,10 +4057,11 @@ fn loadObject( switch (elf.shdrPtr(shndx.*)) { inline else => |shdr| { const old_size = elf.targetLoad(&shdr.size); - elf.targetStore(&shdr.size, @intCast(old_size + section.shdr.size)); + const new_size = old_size + section.shdr.size; + elf.targetStore(&shdr.size, @intCast(new_size)); + elf.updateInitFiniArraySectionSize(shndx.*, init_fini_section_name, @"type", new_size); }, } - try shndx.get(elf).ni.resized(gpa, &elf.mf); break :shndx shndx.*; }, .has_file_bits = true, @@ -3482,7 +4093,8 @@ fn loadObject( // zero-based. This will eventually be updated by `flushMoved`. .vaddr = 0, .node = ni, - .first_reloc = .none, + .first_symbol_reloc = .none, + .first_got_reloc = .none, }; elf.synth_prog_node.increaseEstimatedTotalItems(1); } @@ -3777,6 +4389,8 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { .DEFAULT, .PROTECTED => {}, } + if (sym.shndx == std.elf.SHN_UNDEF) continue; + if (sym.name >= dynstr.len) { return diags.failParse(path, "bad symbol name string", .{}); } @@ -3789,37 +4403,45 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { // If there's already an undefined symbol by this name of type STT_NOTYPE, populate // its type now. - update_sym_type: { - const global_ptr = elf.globals.strong_undef.getPtr(name) orelse - elf.globals.weak_undef.getPtr(name) orelse - break :update_sym_type; + const global_ptr = elf.globals.strong_undef.getPtr(name) orelse + elf.globals.weak_undef.getPtr(name) orelse + continue; - if (global_ptr.dynsym_index == 0) break :update_sym_type; + if (global_ptr.dynsym_index == 0) continue; - const sym_ptr = @field(elf.symPtr(global_ptr.symtab_index), @tagName(class)); - switch (elf.targetLoad(&sym_ptr.other).visibility) { - .HIDDEN, .INTERNAL, .PROTECTED => break :update_sym_type, - .DEFAULT => {}, - } + const sym_ptr = @field(elf.symPtr(global_ptr.symtab_index), @tagName(class)); + switch (elf.targetLoad(&sym_ptr.other).visibility) { + .HIDDEN, .INTERNAL, .PROTECTED => continue, + .DEFAULT => {}, + } - const cur_info = elf.targetLoad(&sym_ptr.info); - if (cur_info.type == .NOTYPE) { - elf.targetStore(&sym_ptr.info, .{ - .bind = cur_info.bind, - .type = sym.info.type, - }); + if (elf.targetLoad(&sym_ptr.shndx) != std.elf.SHN_UNDEF) continue; - const dynsym_ptr = @field(elf.dynsymPtr(global_ptr.dynsym_index), @tagName(class)); - elf.targetStore(&dynsym_ptr.info, .{ - .bind = elf.targetLoad(&dynsym_ptr.info).bind, - .type = sym.info.type, - }); + const cur_info = elf.targetLoad(&sym_ptr.info); + if (cur_info.type == .NOTYPE) { + const new_type: std.elf.STT = switch (sym.info.type) { + .GNU_IFUNC => .FUNC, + else => |t| t, + }; - if (sym.info.type == .FUNC) { - // We've just determined that this symbol actually needs a PLT entry. - elf.addPltEntry(name, global_ptr.dynsym_index); - // TODO: we therefore need to re-apply PLT32 relocs for that symbol! - } + elf.targetStore(&sym_ptr.info, .{ + .bind = cur_info.bind, + .type = new_type, + }); + + const dynsym_ptr = @field(elf.dynsymPtr(global_ptr.dynsym_index), @tagName(class)); + elf.targetStore(&dynsym_ptr.info, .{ + .bind = elf.targetLoad(&dynsym_ptr.info).bind, + .type = new_type, + }); + + // If we just turned this into an STT_FUNC symbol, then we have determined + // that it needs a PLT entry. + if (new_type == .FUNC) { + elf.addPltEntry(name, global_ptr.dynsym_index); + // ...and therefore, we need to re-apply that symbol's relocations, as + // some might be targeting its PLT entry. + global_ptr.symtab_index.applyTargetRelocs(elf); } } } @@ -3934,6 +4556,29 @@ fn createInitFiniArraySection( ), }; } +fn updateInitFiniArraySectionSize( + elf: *Elf, + shndx: Section.Index, + comptime name: []const u8, + @"type": std.elf.SHT, + new_size: u64, +) void { + if (elf.shndx.dynamic != .UNDEF) { + const arraysz_dyn_key: u32 = switch (@"type") { + .INIT_ARRAY => std.elf.DT_INIT_ARRAYSZ, + .FINI_ARRAY => std.elf.DT_FINI_ARRAYSZ, + .PREINIT_ARRAY => std.elf.DT_PREINIT_ARRAYSZ, + else => unreachable, + }; + elf.updateDynamicEntry(arraysz_dyn_key, new_size); + } + + const end_vaddr: u64 = switch (elf.shdrPtr(shndx)) { + inline else => |shdr| shndx.vaddr(elf) + elf.targetLoad(&shdr.size), + }; + const end_sym_name = elf.string(.strtab, "__" ++ name ++ "_end") catch unreachable; // string definitely already exists + Symbol.Id.global(end_sym_name).flushMoved(elf, end_vaddr); +} pub fn prelink(elf: *Elf, prog_node: std.Progress.Node) !void { _ = prog_node; @@ -4073,17 +4718,15 @@ fn prelinkInner(elf: *Elf) !void { ); dynamic_index += 2; } - const rela_dyn_shndx = elf.shndx.got.get(elf).rela_shndx; - const rela_plt_shndx = elf.shndx.got_plt.get(elf).rela_shndx; dynamic_entries[dynamic_index..][0..12].* = .{ - .{ std.elf.DT_RELA, @intCast(rela_dyn_shndx.vaddr(elf)) }, + .{ std.elf.DT_RELA, @intCast(elf.shndx.rela_dyn.vaddr(elf)) }, .{ std.elf.DT_RELASZ, elf.targetLoad( - &@field(elf.shdrPtr(rela_dyn_shndx), @tagName(ct_class)).size, + &@field(elf.shdrPtr(elf.shndx.rela_dyn), @tagName(ct_class)).size, ) }, .{ std.elf.DT_RELAENT, @sizeOf(ElfN.Rela) }, - .{ std.elf.DT_JMPREL, @intCast(rela_plt_shndx.vaddr(elf)) }, + .{ std.elf.DT_JMPREL, @intCast(elf.shndx.rela_plt.vaddr(elf)) }, .{ std.elf.DT_PLTRELSZ, elf.targetLoad( - &@field(elf.shdrPtr(rela_plt_shndx), @tagName(ct_class)).size, + &@field(elf.shdrPtr(elf.shndx.rela_plt), @tagName(ct_class)).size, ) }, .{ std.elf.DT_PLTGOT, @intCast(elf.shndx.got_plt.vaddr(elf)) }, .{ std.elf.DT_PLTREL, std.elf.DT_RELA }, @@ -4100,19 +4743,19 @@ fn prelinkInner(elf: *Elf) !void { if (elf.targetEndian() != native_endian) for (dynamic_entries) |*dynamic_entry| std.mem.byteSwapAllFields(@TypeOf(dynamic_entry.*), dynamic_entry); - elf.first_dynamic_reloc = @enumFromInt(elf.relocs.items.len); + elf.dynamic_first_symbol_reloc = @enumFromInt(elf.symbol_relocs.items.len); try elf.ensureUnusedRelocCapacity(dynamic_ni, 5); elf.addRelocAssumeCapacity( dynamic_ni, @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 12) + 1), - .local(rela_dyn_shndx.get(elf).lsi), + .local(elf.shndx.rela_dyn.get(elf).lsi), 0, .absAddr(elf), ); elf.addRelocAssumeCapacity( dynamic_ni, @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 9) + 1), - .local(rela_plt_shndx.get(elf).lsi), + .local(elf.shndx.rela_plt.get(elf).lsi), 0, .absAddr(elf), ); @@ -4216,7 +4859,11 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { .type = .SECTION, .shndx = shndx, }) else .null; - elf.shdrs.appendAssumeCapacity(.{ .lsi = lsi, .ni = ni, .rela_shndx = .UNDEF, .rela_free = .none }); + elf.shdrs.appendAssumeCapacity(.{ .lsi = lsi, .ni = ni, .rela = switch (opts.type) { + .REL => unreachable, + .RELA => .{ .free_head = .none }, + else => .{ .shndx = .UNDEF }, + } }); elf.nodes.appendAssumeCapacity(.{ .section = shndx }); const offset = ni.fileLocation(&elf.mf, false).offset; switch (elf.shdrPtr(shndx)) { @@ -4242,13 +4889,14 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct { fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize) !void { if (len == 0) return; const gpa = elf.base.comp.gpa; - try elf.relocs.ensureUnusedCapacity(gpa, len); + try elf.symbol_relocs.ensureUnusedCapacity(gpa, len); + try elf.got_relocs.ensureUnusedCapacity(gpa, len); const class = elf.identClass(); - const rela_shndx, const rela_len = rela: switch (elf.ehdrField(.type)) { + switch (elf.ehdrField(.type)) { .NONE, .CORE, _ => unreachable, .REL => { const shndx = elf.getNodeShndx(node); - if (shndx.get(elf).rela_shndx == .UNDEF) { + if (shndx.get(elf).rela.shndx == .UNDEF) { var bfa_buf: [32]u8 = undefined; var bfa: std.heap.BufferFirstAllocator = .init(&bfa_buf, gpa); const allocator = bfa.allocator(); @@ -4275,33 +4923,28 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize) .node_align = elf.mf.flags.block_size, }); elf.section_by_name.putAssumeCapacityNoClobber(rela_shndx.name(elf), {}); - shndx.get(elf).rela_shndx = rela_shndx; + shndx.get(elf).rela.shndx = rela_shndx; } - break :rela .{ shndx.get(elf).rela_shndx, len }; + try shndx.get(elf).rela.shndx.relaEnsureAdditionalCapacity(elf, len); }, - .EXEC, .DYN => switch (elf.got.tlsld) { - _ => return, - .none => if (elf.shndx.dynamic != .UNDEF) { - try elf.mf.updates.ensureUnusedCapacity(gpa, 1); - const got_ni = elf.shndx.got.get(elf).ni; - _, const got_node_size = got_ni.location(&elf.mf).resolve(&elf.mf); - const got_size = switch (class) { - .NONE, _ => unreachable, - inline else => |ct_class| (elf.got.len + 2) * @sizeOf(ct_class.ElfN().Addr), - }; - if (got_size > got_node_size) - try got_ni.resize(&elf.mf, gpa, got_size +| got_size / MappedFile.growth_factor); - break :rela .{ elf.shndx.got.get(elf).rela_shndx, 1 }; - } else return, + .EXEC, .DYN => { + try elf.tls_size_symbol_relocs.ensureUnusedCapacity(gpa, len); + const new_got_entries = len * 2; // at worst, every reloc is a new TLSGD + try elf.got.ensureUnusedCapacity(gpa, new_got_entries); + const got_ni = elf.shndx.got.get(elf).ni; + _, const got_node_size = got_ni.location(&elf.mf).resolve(&elf.mf); + const need_got_size = switch (class) { + .NONE, _ => unreachable, + inline else => |ct_class| (elf.got.count() + new_got_entries) * @sizeOf(ct_class.ElfN().Addr), + }; + if (need_got_size > got_node_size) + try got_ni.resize(&elf.mf, gpa, need_got_size +| need_got_size / MappedFile.growth_factor); + + if (elf.shndx.dynamic != .UNDEF) { + try elf.shndx.rela_dyn.relaEnsureAdditionalCapacity(elf, new_got_entries); + } }, - }; - const rela_ni = rela_shndx.get(elf).ni; - _, const rela_node_size = rela_ni.location(&elf.mf).resolve(&elf.mf); - const rela_size = switch (elf.shdrPtr(rela_shndx)) { - inline else => |shdr| elf.targetLoad(&shdr.size) + elf.targetLoad(&shdr.entsize) * rela_len, - }; - if (rela_size > rela_node_size) - try rela_ni.resize(&elf.mf, gpa, rela_size +| rela_size / MappedFile.growth_factor); + } } fn addRelocAssumeCapacity( elf: *Elf, @@ -4309,123 +4952,443 @@ fn addRelocAssumeCapacity( offset: u64, target: Symbol.Id, addend: i64, - @"type": Reloc.Type, + @"type": MachineRelocType, ) void { assert(node != .none); - const ri: Reloc.Index = @enumFromInt(elf.relocs.items.len); - const next: Reloc.Index = next: { - const target_ptr = target.index(elf).ptr(elf); - const next = target_ptr.first_target_reloc; - target_ptr.first_target_reloc = ri; - break :next next; + switch (elf.ehdrField(.type)) { + .NONE, .CORE, _ => unreachable, + .REL => { + const rela_shndx = elf.getNodeShndx(node).get(elf).rela.shndx; + const rela_index = rela_shndx.relaAddOneAssumeCapacity(elf, .{ + .type = @"type", + // This field needs to equal the offset into the section, which is *not* necessarily + // the same thing as our `offset`, which is the offset into `node`. We could compute + // the section offset now, but there's no point, because `flushMovedNodeRelocs` will + // eventually do it for us anyway, so just init to 0. + .offset = 0, + .raw_sym_index = @intFromEnum(target.index(elf)), + .addend = addend, + }); + const ri: SymbolReloc.Index = @enumFromInt(elf.symbol_relocs.items.len); + const next: SymbolReloc.Index = next: { + const target_ptr = target.index(elf).ptr(elf); + const next = target_ptr.first_target_reloc; + target_ptr.first_target_reloc = ri; + break :next next; + }; + if (next != .none) { + next.get(elf).prev = ri; + } + elf.symbol_relocs.appendAssumeCapacity(.{ + .node = node, + .offset = offset, + .type = .write_rela, + .target = target, + .addend = addend, + .next = next, + .prev = .none, + .rela_index = rela_index.toOptional(), + }); + }, + + .DYN, .EXEC => switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => switch (@"type".X86_64) { + _, + .NONE, + .COPY, + .GLOB_DAT, + .JUMP_SLOT, + .RELATIVE64, + .RELATIVE, + .IRELATIVE, + .@"16", + .PC16, + .@"8", + .PC8, + .DTPMOD64, + .GOTPLT64, + => @panic("TODO: error for illegal or unsupported input relocation"), + + // TODO: the psABI links to https://www.fsfla.org/~lxoliva/writeups/TLS/RFC-TLSDESC-x86.txt + .GOTPC32_TLSDESC => @panic("TODO: R_X86_64_GOTPC32_TLSDESC"), + .TLSDESC_CALL => @panic("TODO: R_X86_64_TLSDESC_CALL"), + .TLSDESC => @panic("TODO: R_X86_64_TLSDESC"), + + // Relocations targeting a symbol + .@"64" => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs64), + .@"32" => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs32), + .@"32S" => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs32s), + .PC64 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel64), + .PC32 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel32), + .PLT32 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .pltrel32), + .SIZE64 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .size64), + .SIZE32 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .size32), + .DTPOFF64 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .dtpoff64), + .DTPOFF32 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .dtpoff32), + .TPOFF64 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff64), + .TPOFF32 => elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff32), + .GOTPC64 => { + const got_sym: Symbol.Id = .local(elf.shndx.got.get(elf).lsi); + return elf.addSymbolRelocAssumeCapacity(node, offset, got_sym, addend, .rel64); + }, + .GOTPC32 => { + const got_sym: Symbol.Id = .local(elf.shndx.got.get(elf).lsi); + return elf.addSymbolRelocAssumeCapacity(node, offset, got_sym, addend, .rel32); + }, + + // TODO: these are the address of an arbitrary symbol (or PLT entry) relative to the + // base of the GOT, which is quite annoying. Luckily, they seem to be rare, so I'm + // probably just going to introduce a set (ArrayHashMap) of SymbolReloc.Index which + // need to be re-applied whenever the GOT moves. + .GOTOFF64 => @panic("TODO: R_X86_64_GOTOFF64"), // offset of symbol from GOT base + .PLTOFF64 => @panic("TODO: R_X86_64_PLTOFF64"), // offset of PLT entry from GOT base (yes, I know, the name is stupid) + + // Relocations targeting a GOT entry + .GOT64 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .offset64), + .GOT32 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .offset32), + .GOTPCREL64 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel64), + .GOTPCREL => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel32), + // TODO: the next two are relaxable to non-GOT relocations, but I haven't figured + // out how to represent relaxations yet. If we want to remove a `GotReloc` and add a + // `SymbolReloc` at some point, we can't do that in `GotReloc.apply`, because that + // function must be idempotent to ensure reproducible binaries. I think we would + // need to do that as soon as the operation is known to be relaxable (e.g. because + // we found a defininition for a non-preemptible symbol). + .GOTPCRELX => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel32), + .REX_GOTPCRELX => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel32), + + .TLSGD => elf.addGotRelocAssumeCapacity(node, offset, .{ .tlsgd0 = target }, addend, .rel32), + .TLSLD => elf.addGotRelocAssumeCapacity(node, offset, .tlsld0, addend, .rel32), + .GOTTPOFF => elf.addGotRelocAssumeCapacity(node, offset, .{ .tpoff = target }, addend, .rel32), + }, + }, + } +} +fn addSymbolRelocAssumeCapacity( + elf: *Elf, + node: MappedFile.Node.Index, + offset: u64, + target: Symbol.Id, + addend: i64, + @"type": SymbolReloc.Type, +) void { + assert(elf.ehdrField(.type) != .REL); + + const rela_index: Section.RelaIndex.Optional = r: { + if (elf.shndx.dynamic == .UNDEF) break :r .none; + const rela_type: MachineRelocType = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => .{ .X86_64 = switch (@"type") { + .write_rela => unreachable, + .abs64 => .@"64", + .abs32 => .@"32", + .abs32s => .@"32S", + .rel64 => .PC64, + .rel32 => .PC32, + .pltrel64 => break :r .none, + .pltrel32 => break :r .none, + .dtpoff64 => .DTPOFF64, + .dtpoff32 => .DTPOFF32, + .tpoff64 => .TPOFF64, + .tpoff32 => .TPOFF32, + .size64 => .SIZE64, + .size32 => .SIZE32, + } }, + }; + const dynsym_index: u32 = switch (target.unwrap()) { + .local => break :r .none, + // TODO: even if the symbol is locally defined, preemption/interposition is a + // possibility, which this condition does not currently consider! + .global => |name| if (elf.globals.strong_def.contains(name) or + elf.globals.weak_def.contains(name)) + { + break :r .none; + } else elf.globalByName(name).?.dynsym_index, + }; + + if (elf.nodeRequiresTextrel(node)) { + elf.textrel_count += 1; + } + + // It currently looks like we need a runtime relocation for this. + break :r elf.shndx.rela_dyn.relaAddOneAssumeCapacity(elf, .{ + .type = rela_type, + // This field needs to equal the offset into the section, which is *not* necessarily + // the same thing as our `offset`, which is the offset into `node`. We could compute + // the section offset now, but there's no point, because `flushMovedNodeRelocs` will + // eventually do it for us anyway, so just init to 0. + .offset = 0, + .raw_sym_index = dynsym_index, + .addend = addend, + }).toOptional(); }; + + const ri: SymbolReloc.Index = @enumFromInt(elf.symbol_relocs.items.len); + const target_ptr = target.index(elf).ptr(elf); + const next = target_ptr.first_target_reloc; + target_ptr.first_target_reloc = ri; if (next != .none) { next.get(elf).prev = ri; } - elf.relocs.addOneAssumeCapacity().* = .{ + elf.symbol_relocs.appendAssumeCapacity(.{ + .node = node, + .offset = offset, + .target = target, + .addend = addend, .type = @"type", - .prev = .none, .next = next, + .prev = .none, + .rela_index = rela_index, + }); + if (@"type".dependsOnTlsSize()) { + elf.tls_size_symbol_relocs.putAssumeCapacityNoClobber(ri, {}); + } +} +fn addGotRelocAssumeCapacity( + elf: *Elf, + node: MappedFile.Node.Index, + offset: u64, + target: GotKey, + addend: i64, + @"type": GotReloc.Type, +) void { + assert(elf.ehdrField(.type) != .REL); + switch (elf.getNode(node)) { + .input_section, + .nav, + .lazy_code, + .lazy_const_data, + => {}, + + .section => unreachable, // cannot contain GOT relocs + .uav => unreachable, // cannot contain GOT relocs + + .file => unreachable, // cannot contain relocs + .ehdr => unreachable, // cannot contain relocs + .shdr => unreachable, // cannot contain relocs + .segment => unreachable, // cannot contain relocs + } + + const gop = elf.got.getOrPutAssumeCapacity(target); + if (!gop.found_existing) { + gop.value_ptr.* = .none; + const maybe_next_key: ?GotKey = switch (target) { + .reserved => null, + .tpoff => null, + .symbol => null, + .tlsld0 => .tlsld1, + .tlsgd0 => |sym| .{ .tlsgd1 = sym }, + .tlsld1 => unreachable, + .tlsgd1 => unreachable, + }; + switch (elf.shdrPtr(elf.shndx.got)) { + inline else => |got_shdr, class| { + const Addr = class.ElfN().Addr; + const old_size = elf.targetLoad(&got_shdr.size); + const new_entry_count = @as(u32, 1) + @intFromBool(maybe_next_key != null); + elf.targetStore(&got_shdr.size, @intCast(old_size + @sizeOf(Addr) * new_entry_count)); + }, + } + if (maybe_next_key) |next_key| { + elf.got.putAssumeCapacityNoClobber(next_key, .none); + elf.updateGotEntry(gop.index); + elf.updateGotEntry(gop.index + 1); + } else { + elf.updateGotEntry(gop.index); + } + } + + elf.got_relocs.appendAssumeCapacity(.{ .node = node, + .offset = offset, .target = target, - .index = index: switch (elf.ehdrField(.type)) { - .NONE, .CORE, _ => unreachable, - .REL => { - const sh = elf.getNodeShndx(node).get(elf); - switch (elf.shdrPtr(sh.rela_shndx)) { - inline else => |shdr, class| { - const Rela = class.ElfN().Rela; - const ent_size = elf.targetLoad(&shdr.entsize); - const rela_slice = sh.rela_shndx.get(elf).ni.slice(&elf.mf); - const index: u32 = if (sh.rela_free.unwrap()) |index| alloc_index: { - const rela: *Rela = @ptrCast(@alignCast( - rela_slice[@intCast(ent_size * index)..][0..@intCast(ent_size)], - )); - sh.rela_free = @enumFromInt(rela.offset); - break :alloc_index index; - } else alloc_index: { - const old_size = elf.targetLoad(&shdr.size); - const new_size = old_size + ent_size; - elf.targetStore(&shdr.size, @intCast(new_size)); - break :alloc_index @intCast(@divExact(old_size, ent_size)); - }; - const rela: *Rela = @ptrCast(@alignCast( - rela_slice[@intCast(ent_size * index)..][0..@intCast(ent_size)], - )); - // The `offset` field here needs to equal the offset into the section, which - // is *not* the same as our `offset` which is the offset into `node`. We - // could calculate it now, but there's no point since `flushMovedNodeRelocs` - // will eventually do that for us anyway. So for now, just set offset to 0. - rela.* = .{ - .offset = 0, - .info = .{ - .type = @intCast(@"type".unwrap(elf)), - .sym = @intCast(@intFromEnum(target.index(elf))), - }, - .addend = @intCast(addend), - }; - if (elf.targetEndian() != native_endian) std.mem.byteSwapAllFields(Rela, rela); - break :index .wrap(index); + .addend = addend, + .type = @"type", + }); +} +fn updateGotEntry(elf: *Elf, got_index: usize) void { + const entry_value: union(enum) { + unsigned: u64, + signed: i64, + reloc: struct { + type: MachineRelocType, + dynsym_index: u32, + }, + } = switch (elf.got.keys()[got_index]) { + .reserved => .{ .unsigned = 0 }, + .tpoff => |sym_id| val: { + // We will break from this block if we require a relocation. + known: { + if (elf.base.comp.config.output_mode != .Exe) { + // Only the executable's per-module TLS block is at a known offset from the + // general TLS pointer. + break :known; + } + switch (sym_id.unwrap()) { + .local => {}, + .global => |name| if (elf.globals.strong_undef.contains(name) or + elf.globals.weak_undef.contains(name)) + { + // This is an external TLS symbol, so we don't know its offset. + break :known; + }, + } + // It's a symbol which we define, the symbol is not interposable because we're the + // executable, and we know our per-module TLS block's offset because we're the + // executable. We therefore know this value! + const tls_phndx = elf.getNode(elf.ni.tls).segment; + const tls_size: u64 = switch (elf.phdrSlice()) { + inline else => |phdr| tls_size: { + assert(elf.targetLoad(&phdr[tls_phndx].type) == .TLS); + break :tls_size elf.targetLoad(&phdr[tls_phndx].memsz); + }, + }; + const sym_value = sym_id.value(elf); + break :val .{ .signed = @bitCast(sym_value -% tls_size) }; + } + break :val .{ + .reloc = .{ + .type = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => .{ .X86_64 = .TPOFF64 }, + }, + .dynsym_index = switch (sym_id.unwrap()) { + .global => |name| elf.globalByName(name).?.dynsym_index, + // TODO: I have no idea if compilers are even allowed to emit this, but if they + // are then I guess we need to add this local symbol to `.dynsym`? + .local => @panic("TODO(Elf2): GOT tpoff entry referencing local symbol"), + }, + }, + }; + }, + .symbol, .tlsgd1 => |sym_id, tag| val: { + const name = switch (sym_id.unwrap()) { + .local => break :val .{ .unsigned = sym_id.value(elf) }, + .global => |name| name, + }; + // If the symbol is *defined* in this module, we might be able to avoid the relocation. + if (elf.globals.strong_def.getPtr(name) orelse + elf.globals.weak_def.getPtr(name)) |global| + { + // We have a definition, but it might be interposable (aka preemptible). There + // are two cases where it is not and so we can (and, in fact, must) elide the + // runtime relocation: + // * We are the executable. Symbols from executables cannot be interposed. + // * The symbol's visibility disallows interposition. + if (elf.base.comp.config.output_mode == .Exe) { + // No relocation needed. + break :val .{ .unsigned = sym_id.value(elf) }; + } + const visibility: std.elf.STV = switch (elf.symPtr(global.symtab_index)) { + inline else => |sym| elf.targetLoad(&sym.other).visibility, + }; + switch (visibility) { + .DEFAULT => {}, + .INTERNAL, .HIDDEN, .PROTECTED => { + // No relocation needed. + break :val .{ .unsigned = sym_id.value(elf) }; }, } - }, - .EXEC, .DYN => { - switch (elf.ehdrField(.machine)) { - else => |machine| @panic(@tagName(machine)), - .AARCH64, .PPC64, .RISCV => {}, - .X86_64 => switch (@"type".X86_64) { - else => {}, - .TLSLD => switch (elf.got.tlsld) { - _ => {}, - .none => if (elf.shndx.dynamic != .UNDEF) { - const tlsld_index = elf.got.len; - elf.got.tlsld = .wrap(tlsld_index); - elf.got.len = tlsld_index + 2; - const got_addr = got_addr: switch (elf.shdrPtr(elf.shndx.got)) { - inline else => |shdr, class| { - const addr_size = @sizeOf(class.ElfN().Addr); - const old_size = addr_size * tlsld_index; - const new_size = old_size + addr_size * 2; - @memset( - elf.shndx.got.get(elf).ni.slice(&elf.mf)[old_size..new_size], - 0, - ); - break :got_addr elf.targetLoad(&shdr.addr) + old_size; - }, + } + break :val .{ .reloc = .{ + .type = if (tag == .symbol) .globDat(elf) else .dtpOffAddr(elf), + .dynsym_index = elf.globalByName(name).?.dynsym_index, + } }; + }, + .tlsgd0 => |sym| switch (elf.shndx.dynamic) { + .UNDEF => .{ .unsigned = 1 }, // TLS module ID for exexcutable + else => .{ + .reloc = .{ + .type = .{ .X86_64 = .DTPMOD64 }, + .dynsym_index = switch (sym.unwrap()) { + .local => 0, + .global => |name| dsi: { + // Like in the `.tlsgd1` case, we need to check for a non-interposable definition. + if (elf.globals.strong_def.getPtr(name) orelse + elf.globals.weak_def.getPtr(name)) |global| + { + if (elf.base.comp.config.output_mode == .Exe) { + break :dsi 0; // non-interposable definition + } + const visibility: std.elf.STV = switch (elf.symPtr(global.symtab_index)) { + inline else => |sym_ptr| elf.targetLoad(&sym_ptr.other).visibility, }; - const rela_dyn_shndx = elf.shndx.got.get(elf).rela_shndx; - const rela_dyn_ni = rela_dyn_shndx.get(elf).ni; - switch (elf.shdrPtr(rela_dyn_shndx)) { - inline else => |shdr, class| { - const Rela = class.ElfN().Rela; - const old_size = elf.targetLoad(&shdr.size); - const new_size = old_size + elf.targetLoad(&shdr.entsize); - elf.targetStore(&shdr.size, new_size); - const rela: *Rela = @ptrCast(@alignCast(rela_dyn_ni - .slice(&elf.mf)[@intCast(old_size)..@intCast(new_size)])); - rela.* = .{ - .offset = @intCast(got_addr), - .info = .{ - .type = @intFromEnum(std.elf.R_X86_64.DTPMOD64), - .sym = 0, - }, - .addend = 0, - }; - if (elf.targetEndian() != native_endian) - std.mem.byteSwapAllFields(Rela, rela); + switch (visibility) { + .DEFAULT => {}, + .INTERNAL, .HIDDEN, .PROTECTED => { + break :dsi 0; // non-interposable definition }, } - rela_dyn_ni.resizedAssumeCapacity(&elf.mf); - }, + } + // `sym` is either undefined or an interposable definition, so use its + // actual dynsym index. + break :dsi elf.globalByName(name).?.dynsym_index; }, }, - } - break :index .none; + }, }, }, - .offset = offset, - .addend = addend, + .tlsld0 => switch (elf.shndx.dynamic) { + .UNDEF => .{ .unsigned = 1 }, // TLS module ID for exexcutable + else => .{ .reloc = .{ + .type = .{ .X86_64 = .DTPMOD64 }, + .dynsym_index = 0, + } }, + }, + .tlsld1 => .{ .unsigned = 0 }, }; + + // First, write to the GOT itself. If we're planning to use a relocation, we'll just write zeroes. + const got_entry_addr: u64 = switch (elf.shdrPtr(elf.shndx.got)) { + inline else => |got_shdr, class| got_entry_addr: { + const addr_size = @sizeOf(class.ElfN().Addr); + const offset = got_index * addr_size; + const entry_ptr: *class.ElfN().Addr = @ptrCast(@alignCast( + elf.shndx.got.get(elf).ni.slice(&elf.mf)[offset..][0..addr_size], + )); + entry_ptr.* = switch (entry_value) { + .unsigned => |x| @intCast(x), + .signed => |x| switch (class) { + .NONE, _ => comptime unreachable, + .@"32" => @bitCast(@as(i32, @intCast(x))), + .@"64" => @bitCast(x), + }, + .reloc => 0, + }; + break :got_entry_addr elf.targetLoad(&got_shdr.addr) + offset; + }, + }; + + // Then, add or remove the relocation entry if needed. + if (elf.shndx.dynamic == .UNDEF) { + // There are no relocations in the output file, so there's no reloc to delete and we can't + // add a reloc in any case. (If we *are* requesting a reloc, it'll be because the value of + // this GOT entry is not yet known, e.g. because a symbol is currently undefined.) + return; + } + if (elf.got.values()[got_index].unwrap()) |rela_index| { + // Clear the old relocation entry (although we might immediately re-use it below). + elf.shndx.rela_dyn.relaDeleteOne(elf, rela_index); + } + elf.got.values()[got_index] = switch (entry_value) { + .unsigned, .signed => .none, // no relocation needed + .reloc => |reloc| elf.shndx.rela_dyn.relaAddOneAssumeCapacity(elf, .{ + .type = reloc.type, + .offset = got_entry_addr, + .raw_sym_index = reloc.dynsym_index, + .addend = 0, + }).toOptional(), + }; +} + +/// Returns whether a `DT_TEXTREL` dynamic entry is needed to have a runtime relocation in `node`. +fn nodeRequiresTextrel(elf: *Elf, node: MappedFile.Node.Index) bool { + const shndx = elf.getNodeShndx(node); + const shf: std.elf.SHF = switch (elf.shdrPtr(shndx)) { + inline else => |shdr| elf.targetLoad(&shdr.flags).shf, + }; + return shf.ALLOC and !shf.WRITE; } pub fn updateNav(elf: *Elf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -4565,6 +5528,11 @@ pub fn flush( if (any_undef) return error.LinkFailure; } + elf.updateDynamicTextrel() catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => |e| return elf.base.comp.link_diags.fail("updateDynamicTextrel failed: {t}", .{e}), + }; + while (try elf.idle(tid)) {} const entry_addr: u64 = entry: { @@ -4595,6 +5563,47 @@ pub fn flush( else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), }; } +fn updateDynamicTextrel(elf: *Elf) !void { + if (elf.shndx.dynamic == .UNDEF) return; + const dynamic_ni = elf.shndx.dynamic.get(elf).ni; + switch (elf.shdrPtr(elf.shndx.dynamic)) { + inline else => |shdr, class| if (elf.textrel_count > 0) { + const cur_size = elf.targetLoad(&shdr.size); + const cur_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( + dynamic_ni.slice(&elf.mf)[0..@intCast(cur_size)], + )); + const has_textrel: bool = for (cur_entries) |*entry| { + if (elf.targetLoad(&entry[0]) == std.elf.DT_TEXTREL) { + break true; + } + } else false; + if (!has_textrel) { + // Add a DT_TEXTREL entry before the final DT_NULL entry. + const new_size = cur_size + @sizeOf([2]class.ElfN().Addr); + _, const node_size = dynamic_ni.location(&elf.mf).resolve(&elf.mf); + if (node_size < new_size) { + try dynamic_ni.resize(&elf.mf, elf.base.comp.gpa, new_size); + } + elf.targetStore(&shdr.size, new_size); + const new_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( + dynamic_ni.slice(&elf.mf)[0..@intCast(new_size)], + )); + const write_entries = new_entries[new_entries.len - 2 ..][0..2]; + assert(elf.targetLoad(&write_entries[0][0]) == std.elf.DT_NULL); + write_entries.* = .{ + .{ std.elf.DT_TEXTREL, 0 }, + .{ std.elf.DT_NULL, 0 }, + }; + if (elf.targetEndian() != native_endian) { + std.mem.byteSwapAllElements([2]class.ElfN().Addr, write_entries); + } + } + } else { + // TODO: remove the DT_TEXTREL entry if there is one, because it's not necessary any + // more. It won't cause any issues having it there, it's just inefficient. + }, + } +} pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { const comp = elf.base.comp; @@ -4660,6 +5669,9 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { break :task; } if (elf.changed_symtab_index.pop()) |kv| { + // We only need to do work in relocatables, because in ELF modules (non-relocatables) + // our `ElfN.Rela` entries use `.dynsym` indices rather than `.symtab` indices, and + // `.dynsym` indices are (at the time of writing) always immutable. if (elf.ehdrField(.type) == .REL) { const sub_prog_node = elf.mf.update_prog_node.start(kv.key.slice(elf), 0); defer sub_prog_node.end(); @@ -4667,7 +5679,11 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { var ri = sym.first_target_reloc; while (ri != .none) { const reloc = ri.get(elf); - reloc.updateTargetIndex(elf); + reloc.relaSection(elf).relaUpdateSym( + elf, + reloc.rela_index.unwrap().?, + @intFromEnum(reloc.target.index(elf)), + ); ri = reloc.next; } break :task; @@ -4876,126 +5892,56 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { .section => |shndx| { try elf.flushFileOffset(ni); const addr = elf.computeNodeVAddr(ni); - switch (elf.shdrPtr(shndx)) { - inline else => |shdr, class| { - const flags = elf.targetLoad(&shdr.flags).shf; - if (flags.ALLOC) { - if (elf.shndx.dynamic != .UNDEF) { - if (shndx == elf.shndx.got) { - const old_addr = elf.targetLoad(&shdr.addr); - const rela_dyn_shndx = shndx.get(elf).rela_shndx; - const relas: []class.ElfN().Rela = @ptrCast(@alignCast( - rela_dyn_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast( - elf.targetLoad(&@field( - elf.shdrPtr(rela_dyn_shndx), - @tagName(class), - ).size), - )], - )); - switch (elf.ehdrField(.machine)) { - else => |machine| @panic(@tagName(machine)), - .AARCH64, .PPC64, .RISCV => {}, - .X86_64 => for (relas) |*rela| switch (@as( - std.elf.R_X86_64, - @enumFromInt(elf.targetLoad(&rela.info).type), - )) { - else => |@"type"| @panic(@tagName(@"type")), - .RELATIVE => {}, - .GLOB_DAT, .DTPMOD64, .DTPOFF64 => elf.targetStore( - &rela.offset, - @intCast(elf.targetLoad(&rela.offset) - old_addr + addr), - ), - }, - } - } else if (shndx == elf.shndx.got_plt) { - const target_endian = elf.targetEndian(); - const old_addr = elf.targetLoad(&shdr.addr); - const rela_plt_shndx = shndx.get(elf).rela_shndx; - const relas: []class.ElfN().Rela = @ptrCast(@alignCast( - rela_plt_shndx.get(elf).ni.slice(&elf.mf)[0..@intCast( - elf.targetLoad(&@field( - elf.shdrPtr(rela_plt_shndx), - @tagName(class), - ).size), - )], - )); - const plt_sec_slice = elf.shndx.plt_sec.get(elf).ni.slice(&elf.mf); - switch (elf.ehdrField(.machine)) { - else => |machine| @panic(@tagName(machine)), - .AARCH64, .PPC64, .RISCV => {}, - .X86_64 => { - for (relas) |*rela| switch (@as( - std.elf.R_X86_64, - @enumFromInt(elf.targetLoad(&rela.info).type), - )) { - else => |@"type"| @panic(@tagName(@"type")), - .JUMP_SLOT => elf.targetStore( - &rela.offset, - @intCast(elf.targetLoad(&rela.offset) - old_addr + addr), - ), - }; - for (0..elf.got.plt.count()) |plt_index| { - const slice = plt_sec_slice[16 * plt_index + 6 ..][0..4]; - std.mem.writeInt( - i32, - slice, - @intCast(@as(i64, @bitCast(@as(u64, @bitCast(@as( - i64, - std.mem.readInt(i32, slice, target_endian), - ))) -% old_addr +% addr))), - target_endian, - ); - } - }, - } - } else if (shndx == elf.shndx.plt_sec) { - const target_endian = elf.targetEndian(); - const old_addr = elf.targetLoad(&shdr.addr); - const plt_sec_slice = ni.slice(&elf.mf); - switch (elf.ehdrField(.machine)) { - else => |machine| @panic(@tagName(machine)), - .AARCH64, .PPC64, .RISCV => {}, - .X86_64 => for (0..elf.got.plt.count()) |plt_index| { - const slice = plt_sec_slice[16 * plt_index + 6 ..][0..4]; - std.mem.writeInt( - i32, - slice, - @intCast(@as(i64, @bitCast(@as(u64, @bitCast(@as( - i64, - std.mem.readInt(i32, slice, target_endian), - ))) -% addr +% old_addr))), - target_endian, - ); - }, - } - } - } - - // Update global symbols targeting this section - if (elf.node_global_symbols.get(ni)) |first_name| { - assert(first_name != .empty); - const old_addr = elf.targetLoad(&shdr.addr); - var name = first_name; - while (name != .empty) { - const global = elf.globalByName(name).?; - const old_sym_addr: u64 = switch (elf.symPtr(global.symtab_index)) { - inline else => |sym| elf.targetLoad(&sym.value), - }; - global.flushMoved(elf, old_sym_addr - old_addr + addr); - name = global.next_in_node; - } - } - - elf.targetStore(&shdr.addr, @intCast(addr)); - shndx.get(elf).lsi.index().flushMoved(elf, addr); - } - - if (shndx == elf.shndx.plt) { - elf.flushMovedNodeRelocs(ni, elf.targetLoad(&shdr.addr), elf.first_plt_reloc); - } else if (shndx == elf.shndx.dynamic) { - elf.flushMovedNodeRelocs(ni, elf.targetLoad(&shdr.addr), elf.first_dynamic_reloc); - } + const old_addr: u64, const flags: std.elf.SHF = switch (elf.shdrPtr(shndx)) { + inline else => |shdr| .{ + elf.targetLoad(&shdr.addr), + elf.targetLoad(&shdr.flags).shf, }, + }; + + if (flags.ALLOC) { + switch (elf.shdrPtr(shndx)) { + inline else => |shdr| elf.targetStore(&shdr.addr, @intCast(addr)), + } + + // Update global symbols targeting this section + if (elf.node_global_symbols.get(ni)) |first_name| { + assert(first_name != .empty); + var name = first_name; + while (name != .empty) { + const global = elf.globalByName(name).?; + const old_sym_addr: u64 = switch (elf.symPtr(global.symtab_index)) { + inline else => |sym| elf.targetLoad(&sym.value), + }; + Symbol.Id.global(name).flushMoved( + elf, + old_sym_addr - old_addr + addr, + ); + name = global.next_in_node; + } + } + + Symbol.Id.local(shndx.get(elf).lsi).flushMoved(elf, addr); + } + + if (shndx == elf.shndx.got) { + const rela_dyn_shndx = elf.shndx.rela_dyn; + for (elf.got.values()) |opt_rela_index| { + const rela_index = opt_rela_index.unwrap() orelse continue; + rela_dyn_shndx.relaAdjustOffset(elf, rela_index, old_addr, addr); + } + for (elf.got_relocs.items) |*reloc| { + reloc.apply(elf); + } + } else if (shndx == elf.shndx.plt) { + elf.flushMovedNodeRelocs(ni, addr, elf.plt_first_symbol_reloc, .none); + elf.flushMovedPltSection(.plt, old_addr, addr); + } else if (shndx == elf.shndx.got_plt) { + elf.flushMovedPltSection(.got_plt, old_addr, addr); + } else if (shndx == elf.shndx.plt_sec) { + elf.flushMovedPltSection(.plt_sec, old_addr, addr); + } else if (shndx == elf.shndx.dynamic) { + elf.flushMovedNodeRelocs(ni, addr, elf.dynamic_first_symbol_reloc, .none); } }, .input_section => |isi| { @@ -5020,7 +5966,10 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { .DEFAULT => elf.targetLoad(&sym.value), }, }; - lsi.index().flushMoved(elf, old_sym_addr - old_section_addr + new_section_addr); + Symbol.Id.local(lsi).flushMoved( + elf, + old_sym_addr - old_section_addr + new_section_addr, + ); } // Update global symbols @@ -5032,26 +5981,38 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void { const old_sym_addr: u64 = switch (elf.symPtr(global.symtab_index)) { inline else => |sym| elf.targetLoad(&sym.value), }; - global.flushMoved(elf, old_sym_addr - old_section_addr + new_section_addr); + Symbol.Id.global(name).flushMoved( + elf, + old_sym_addr - old_section_addr + new_section_addr, + ); name = global.next_in_node; } } - elf.flushMovedNodeRelocs(ni, new_section_addr, isi.ptrConst(elf).first_reloc); + elf.flushMovedNodeRelocs( + ni, + new_section_addr, + isi.ptrConst(elf).first_symbol_reloc, + isi.ptrConst(elf).first_got_reloc, + ); }, inline .nav, .uav, .lazy_code, .lazy_const_data => |mi| { const new_addr = elf.computeNodeVAddr(ni); - mi.symbol(elf).index().flushMoved(elf, new_addr); + Symbol.Id.local(mi.symbol(elf)).flushMoved(elf, new_addr); if (elf.node_global_symbols.get(ni)) |first_name| { assert(first_name != .empty); var name = first_name; while (name != .empty) { - const global = elf.globalByName(name).?; - global.flushMoved(elf, new_addr); - name = global.next_in_node; + Symbol.Id.global(name).flushMoved(elf, new_addr); + name = elf.globalByName(name).?.next_in_node; } } - elf.flushMovedNodeRelocs(ni, new_addr, mi.firstReloc(elf)); + elf.flushMovedNodeRelocs( + ni, + new_addr, + mi.firstSymbolReloc(elf), + mi.firstGotReloc(elf), + ); }, } try ni.childrenMoved(elf.base.comp.gpa, &elf.mf); @@ -5079,6 +6040,27 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void { }, .TLS => { elf.targetStore(&ph.memsz, @intCast(size)); + // TPOFF relocations care about the size of the TLS segment. Re-apply + // those, and also update any GOT entries from GOTTPOFF relocations. + for (elf.tls_size_symbol_relocs.keys()) |reloc| { + reloc.get(elf).apply(elf); + } + for (elf.got.keys(), 0..) |got_key, got_index| { + switch (got_key) { + .reserved, + .symbol, + .tlsld0, + .tlsld1, + .tlsgd0, + .tlsgd1, + => { + @branchHint(.likely); + continue; + }, + + .tpoff => elf.updateGotEntry(got_index), + } + } return ni.childrenMoved(elf.base.comp.gpa, &elf.mf); }, } @@ -5113,110 +6095,28 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void { }, }, .section => |shndx| switch (elf.shdrPtr(shndx)) { - inline else => |shdr, class| { + inline else => |shdr| { switch (elf.targetLoad(&shdr.type)) { else => unreachable, + .NULL => if (size > 0) elf.targetStore(&shdr.type, .PROGBITS), .PROGBITS => if (size == 0) elf.targetStore(&shdr.type, .NULL), - .SYMTAB, .DYNAMIC, .REL, .DYNSYM => return, - .INIT_ARRAY => { - assert(shndx == elf.shndx.init_array); - if (elf.shndx.dynamic != .UNDEF) { - const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( - elf.shndx.dynamic.get(elf).ni.slice(&elf.mf), - )); - for (dynamic_entries) |*dynamic_entry| - switch (elf.targetLoad(&dynamic_entry[0])) { - else => {}, - std.elf.DT_INIT_ARRAYSZ => dynamic_entry[1] = shdr.size, - }; - } - const end_sym_index = elf.globalByName(elf.string(.strtab, "__init_array_end") catch unreachable).?.symtab_index; - const end_sym_ptr = @field(elf.symPtr(end_sym_index), @tagName(class)); - const end_vaddr = shndx.vaddr(elf) + elf.targetLoad(&shdr.size); - elf.targetStore(&end_sym_ptr.value, @intCast(end_vaddr)); - end_sym_index.flushMoved(elf, end_vaddr); - return; - }, - .FINI_ARRAY => { - assert(shndx == elf.shndx.fini_array); - if (elf.shndx.dynamic != .UNDEF) { - const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( - elf.shndx.dynamic.get(elf).ni.slice(&elf.mf), - )); - for (dynamic_entries) |*dynamic_entry| - switch (elf.targetLoad(&dynamic_entry[0])) { - else => {}, - std.elf.DT_FINI_ARRAYSZ => dynamic_entry[1] = shdr.size, - }; - } - const end_sym_index = elf.globalByName(elf.string(.strtab, "__fini_array_end") catch unreachable).?.symtab_index; - const end_sym_ptr = @field(elf.symPtr(end_sym_index), @tagName(class)); - const end_vaddr = shndx.vaddr(elf) + elf.targetLoad(&shdr.size); - elf.targetStore(&end_sym_ptr.value, @intCast(end_vaddr)); - end_sym_index.flushMoved(elf, end_vaddr); - return; - }, - .PREINIT_ARRAY => { - assert(shndx == elf.shndx.preinit_array); - if (elf.shndx.dynamic != .UNDEF) { - const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( - elf.shndx.dynamic.get(elf).ni.slice(&elf.mf), - )); - for (dynamic_entries) |*dynamic_entry| - switch (elf.targetLoad(&dynamic_entry[0])) { - else => {}, - std.elf.DT_PREINIT_ARRAYSZ => dynamic_entry[1] = shdr.size, - }; - } - const end_sym_index = elf.globalByName(elf.string(.strtab, "__preinit_array_end") catch unreachable).?.symtab_index; - const end_sym_ptr = @field(elf.symPtr(end_sym_index), @tagName(class)); - const end_vaddr = shndx.vaddr(elf) + elf.targetLoad(&shdr.size); - elf.targetStore(&end_sym_ptr.value, @intCast(end_vaddr)); - end_sym_index.flushMoved(elf, end_vaddr); - return; - }, - .STRTAB => { - if (elf.shndx.dynamic != .UNDEF) { - if (shndx == elf.shndx.dynstr) { - const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( - elf.shndx.dynamic.get(elf).ni.slice(&elf.mf), - )); - for (dynamic_entries) |*dynamic_entry| - switch (elf.targetLoad(&dynamic_entry[0])) { - else => {}, - std.elf.DT_STRSZ => dynamic_entry[1] = shdr.size, - }; - } - } - return; - }, - .RELA => { - if (elf.shndx.dynamic != .UNDEF) { - if (shndx == elf.shndx.got.get(elf).rela_shndx) { - const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( - elf.shndx.dynamic.get(elf).ni.slice(&elf.mf), - )); - for (dynamic_entries) |*dynamic_entry| - switch (elf.targetLoad(&dynamic_entry[0])) { - else => {}, - std.elf.DT_RELASZ => dynamic_entry[1] = shdr.size, - }; - } else if (shndx == elf.shndx.got_plt.get(elf).rela_shndx) { - const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( - elf.shndx.dynamic.get(elf).ni.slice(&elf.mf), - )); - for (dynamic_entries) |*dynamic_entry| - switch (elf.targetLoad(&dynamic_entry[0])) { - else => {}, - std.elf.DT_PLTRELSZ => dynamic_entry[1] = shdr.size, - }; - } - } - return; - }, + + .INIT_ARRAY, + .FINI_ARRAY, + .PREINIT_ARRAY, + .STRTAB, + .SYMTAB, + .DYNAMIC, + .REL, + .RELA, + .DYNSYM, + => return, } - if (shndx != elf.shndx.plt) { + if (shndx != elf.shndx.plt and + shndx != elf.shndx.got and + shndx != elf.shndx.got_plt) + { elf.targetStore(&shdr.size, @intCast(size)); } }, @@ -5224,6 +6124,85 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void { .input_section, .nav, .uav, .lazy_code, .lazy_const_data => {}, } } +fn updateDynamicEntry(elf: *Elf, key: u32, new_val: u64) void { + switch (elf.shdrPtr(elf.shndx.dynamic)) { + inline else => |shdr, class| { + const dynamic_size = elf.targetLoad(&shdr.size); + const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast( + elf.shndx.dynamic.get(elf).ni.slice(&elf.mf)[0..@intCast(dynamic_size)], + )); + for (dynamic_entries) |*dynamic_entry| { + if (elf.targetLoad(&dynamic_entry[0]) == key) { + elf.targetStore(&dynamic_entry[1], @intCast(new_val)); + } + } + }, + } +} +fn flushMovedPltSection(elf: *Elf, which: enum { plt, plt_sec, got_plt }, old_addr: u64, addr: u64) void { + const target_endian = elf.targetEndian(); + switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => { + switch (which) { + .plt => return, + .plt_sec => { + // Re-apply all PLT relocations. If a symbol is in the PLT then the majority of + // its relocations are probably going through the PLT, so we don't bother with + // specific tracking for PLT relocations---instead just re-apply all relocations + // targeting symbols with PLT entries. + for (elf.plt.keys()) |sym| { + sym.index(elf).applyTargetRelocs(elf); + } + // We also need to update all of the references from `.plt.sec` to `.got.plt`. + // However, if there's also a flush pending for `.got.plt`, don't bother doing + // this now, because we'll do it when `.got.plt` is flushed anyway. + if (elf.shndx.got_plt.get(elf).ni.hasMoved(&elf.mf)) { + return; + } + // Exit this `switch` to update those references. + }, + .got_plt => { + // Update the offsets of the relocation entries in `.rela.plt`. + const rela_plt_shndx = elf.shndx.rela_plt; + for (0..elf.plt.count()) |plt_index| { + if (elf.pltEntryIsDead(plt_index)) continue; + rela_plt_shndx.relaAdjustOffset(elf, @enumFromInt(plt_index), old_addr, addr); + } + // We also need to update all of the references from `.plt.sec` to `.got.plt`. + // However, if there's also a flush pending for `.plt.sec`, don't bother doing + // this now, because we'll do it when `.plt.sec` is flushed anyway. + if (elf.shndx.plt_sec.get(elf).ni.hasMoved(&elf.mf)) { + return; + } + // Exit this `switch` to update those references. + }, + } + // We are updating the references from `.plt.sec` to `.got.plt`. + const got_plt_addr = elf.shndx.got_plt.vaddr(elf); + const plt_sec_addr = elf.shndx.plt_sec.vaddr(elf); + const plt_sec_slice = elf.shndx.plt_sec.get(elf).ni.slice(&elf.mf); + switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| { + const Addr = class.ElfN().Addr; + for (0..elf.plt.count()) |plt_index| { + const plt_sec_offset = 16 * plt_index; + const got_plt_offset = @sizeOf(Addr) * (3 + plt_index); + std.mem.writeInt( + i32, + plt_sec_slice[plt_sec_offset + 6 ..][0..4], + @intCast(@as(i64, @bitCast( + (got_plt_addr + got_plt_offset) -% (plt_sec_addr + plt_sec_offset + 10), + ))), + target_endian, + ); + } + }, + } + }, + } +} pub fn updateExports( elf: *Elf, -- 2.54.0