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,