commit 7cfe6c7c13e9931c162edc88d7e0c79fe17752f2 (tree)
parent f66a9be7cc4e63ed9328d3488df7e7e7784dce49
Author: Matthew Lugg <mlugg@mlugg.co.uk>
Date: Fri, 29 May 2026 14:01:38 +0100
Elf2: implement copy relocations
These are a way of avoiding text relocations in dynamic executables, but
are also necessary for correctness in some cases due to PC32 relocations
overflowing with rtld's default mmap behavior. See implementation for
more details.
This fixes the "Symbol 'x' causes overflow in R_X86_64_PC32 relocation"
errors which were previously emitted quite often by rtld on dynamic
executables linked with Elf2. Notably, this means the Zig compiler can
now be linked against LLVM, statically or dynamically, with no errors
and no workarounds needed.
Also, fix a possible (although rare-ish) memory corruption, caused by a
node slice being accessed after the slice was potentially invalidated by
the addition of a new node. It seems that this was occasionally
manifesting as a corrupted symbol table entry leading to an integer
overflow when applying a relocation.
Diffstat:
| M | src/link/Elf2.zig | | | 714 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
1 file changed, 489 insertions(+), 225 deletions(-)
diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig
@@ -49,6 +49,20 @@ globals: struct {
strong_undef: std.array_hash_map.Auto(String(.strtab), Symbol.Global),
weak_undef: std.array_hash_map.Auto(String(.strtab), Symbol.Global),
},
+/// Key is the name of an undef global for which we have created a "copy relocation" (`R_*_COPY`).
+copied_globals: std.array_hash_map.Auto(String(.strtab), struct {
+ node: MappedFile.Node.Index,
+ /// The index of this global's runtime relocation in `.rela.dyn`.
+ rela_index: Section.RelaIndex,
+}),
+/// Key is the name of an undef global for which we would *like* to create a copy relocation
+/// (`R_*_COPY`), but cannot because we have not seen an appropriate definition in a linked DSO yet.
+///
+/// Therefore, if, when scanning a DSO input, we discover a definition for one of these symbols, we
+/// will remove it from this map and call `
+///
+/// which have not created
+want_copied_globals: std.array_hash_map.Auto(String(.strtab), void),
/// Key is a node which is a valid `Symbol.node` value, value is the name of the first global symbol
/// in that node. That symbol is the head of a linked list: see `Symbol.Global.next_in_node`.
///
@@ -57,14 +71,29 @@ globals: struct {
/// We use a separate hash map for this data rather than storing it in `navs` etc to save memory,
/// because the vast majority of nodes which can export global symbols actually will not.
node_global_symbols: std.array_hash_map.Auto(MappedFile.Node.Index, String(.strtab)),
-/// Contains all globals symbols defined in any needed DSO. This map serves two purposes:
+/// Contains all globals symbols defined in any needed DSO. This map serves three purposes:
+///
+/// * If we discover an undefined reference to one of these symbols, we know whether the symbol has
+/// type `STT_FUNC`, in which case we will create a PLT entry.
///
-/// * If we discover an undefined reference to one of these symbols, we will know the associated
-/// symbol type, which is important because it may cause us to create a PLT entry.
+/// * If we discover a direct relocation (i.e. no GOT or PLT indirection) targeting one of these
+/// symbols, we know whether the symbol has type `STT_OBJECT` and we know its size and alignment,
+/// so we can emit a copy relocation for that symbol instead of using a text relocation.
///
/// * When emitting a dynamic executable, we can detect which undefined references are resolved by a
/// linked DSO, so can emit "undefined global symbol" errors for any other undefined references.
-dso_globals: std.array_hash_map.Auto(String(.strtab), std.elf.STT),
+dso_globals: std.array_hash_map.Auto(String(.strtab), struct {
+ type: std.elf.STT,
+ size: u64,
+ /// This is usually unnecessary, but if a symbol is given a copy relocation (`R_*_COPY`) and so
+ /// becomes a part of the executable's address space despite being defined by a different DSO,
+ /// we need to know its alignment requirement so that we don't break other code. This isn't
+ /// actually stored on the symbol---instead we compute a maximum alignment from the alignment of
+ /// the section containing the symbol, and the symbol's offset within the section. I know this
+ /// sounds like a terrible hack, but it is *genuinely* how you're supposed to do this. Copy
+ /// relocations suck.
+ alignment: std.mem.Alignment,
+}),
shstrtab: StringTable,
strtab: StringTable,
dynstr: StringTable,
@@ -136,22 +165,24 @@ synth_prog_node: std.Progress.Node,
input_prog_node: std.Progress.Node,
const Node = union(enum) {
- /// Cannot contain relocations.
file,
- /// Cannot contain relocations.
ehdr,
- /// Cannot contain relocations.
shdr,
- /// Cannot contain relocations.
segment: u32,
/// The section '.plt' may contain relocations via `elf.plt_first_symbol_reloc`.
///
/// The section '.dynamic' may contain relocations via `elf.dynamic_first_symbol_reloc`.
- ///
- /// Otherwise, cannot contain relocations.
section: Section.Index,
/// May contain relocations.
input_section: InputSection.Index,
+ /// Value is the name of a global which has an entry in `elf.copied_globals`, so, a global for
+ /// which we have emitted a copy relocation.
+ ///
+ /// TODO it would be better to emit these into `.bss` or `.bss.rel.ro`, once we support those.
+ ///
+ /// TODO: currently, the `elf.copied_globals` entry may not be there---this case exists because
+ /// `MappedFile` does not (yet?) support deleting nodes. See logic in `setGlobalSymbolValue`.
+ copied_global: String(.strtab),
/// May contain relocations.
nav: NavMapIndex,
/// May contain relocations.
@@ -746,6 +777,7 @@ const GotReloc = struct {
.ehdr => unreachable,
.shdr => unreachable,
.segment => unreachable,
+ .copied_global => unreachable,
.section => |shndx| shndx.vaddr(elf),
.input_section => |isi| isi.ptrConst(elf).vaddr,
inline .nav,
@@ -808,6 +840,15 @@ pub const MachineRelocType = union {
.X86_64 => .{ .X86_64 = .NONE },
};
}
+ pub fn copy(elf: *Elf) MachineRelocType {
+ return switch (elf.ehdrField(.machine)) {
+ else => unreachable,
+ .AARCH64 => .{ .AARCH64 = .COPY },
+ .PPC64 => .{ .PPC64 = .COPY },
+ .RISCV => .{ .RISCV = .COPY },
+ .X86_64 => .{ .X86_64 = .COPY },
+ };
+ }
pub fn jumpSlot(elf: *Elf) MachineRelocType {
return switch (elf.ehdrField(.machine)) {
else => unreachable,
@@ -973,6 +1014,7 @@ const SymbolReloc = struct {
.ehdr => unreachable,
.shdr => unreachable,
.segment => unreachable,
+ .copied_global => unreachable,
.section => |shndx| shndx.vaddr(elf),
.input_section => |isi| isi.ptrConst(elf).vaddr,
inline .nav,
@@ -984,11 +1026,9 @@ const SymbolReloc = struct {
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 sym_value: u64 = reloc.target.value(elf);
+ const sym_size: u64 = switch (elf.symPtr(reloc.target.index(elf))) {
+ inline else => |target_sym| elf.targetLoad(&target_sym.size),
};
const target_value = sym_value +% @as(u64, @bitCast(reloc.addend));
type: switch (reloc.type) {
@@ -1114,6 +1154,12 @@ const SymbolReloc = struct {
fn delete(reloc: *SymbolReloc, elf: *Elf, index: SymbolReloc.Index) void {
assert(index.get(elf) == reloc);
+
+ reloc.deleteOutputRel(elf);
+ if (reloc.type.dependsOnTlsSize()) {
+ assert(elf.tls_size_symbol_relocs.swapRemove(index));
+ }
+
switch (reloc.prev) {
.none => {
const target_ptr = reloc.target.index(elf).ptr(elf);
@@ -1126,18 +1172,25 @@ const SymbolReloc = struct {
.none => {},
else => |next| next.get(elf).prev = reloc.prev,
}
- if (reloc.rela_index.unwrap()) |rela_index| {
- reloc.relaSection(elf).relaDeleteOne(elf, rela_index);
- switch (elf.nodeWantsDsoRelocation(reloc.node)) {
+
+ reloc.* = undefined;
+ }
+
+ /// If `reloc.rela_index` is populated, reset it to `.none` and delete the relocation, updating
+ /// `elf.textrel_count` if necessary.
+ fn deleteOutputRel(reloc: *SymbolReloc, elf: *Elf) void {
+ const rela_index = reloc.rela_index.unwrap() orelse return;
+ reloc.relaSection(elf).relaDeleteOne(elf, rela_index);
+ switch (elf.ehdrField(.type)) {
+ .NONE, .CORE, _ => unreachable,
+ .REL => {},
+ .EXEC, .DYN => switch (elf.nodeWantsDsoRelocation(reloc.node)) {
.no => unreachable, // there *was* a dynamic relocation!
.yes => {},
.yes_textrel => elf.textrel_count -= 1,
- }
- }
- if (reloc.type.dependsOnTlsSize()) {
- assert(elf.tls_size_symbol_relocs.swapRemove(index));
+ },
}
- reloc.* = undefined;
+ reloc.rela_index = .none;
}
};
@@ -1468,7 +1521,9 @@ fn addGlobalSymbolAssumeCapacity(elf: *Elf, opts: AddGlobalSymbolOptions) error{
};
const @"type": std.elf.STT = switch (opts.type) {
- .NOTYPE => elf.dso_globals.get(opts.name.strtab) orelse .NOTYPE,
+ .NOTYPE => if (elf.dso_globals.get(opts.name.strtab)) |dso_global| t: {
+ break :t dso_global.type;
+ } else .NOTYPE,
else => |t| t,
};
@@ -1605,6 +1660,23 @@ fn setGlobalSymbolValue(
assert(global_ptr.prev_in_node == .empty);
}
+ if (elf.copied_globals.fetchSwapRemove(global_name)) |copied_global_kv| {
+ // This is a quite rare case: there was a definition for this symbol in a shared library
+ // input, and we ended up emitting a copy relocation for it, but we've now got our *own*
+ // definition which replaces it. We know that our definition cannot be preempted because we
+ // are the executable (only executables can have copy relocations!), so we definitely do not
+ // need the copy relocation.
+
+ // All we actually need to do is remove the entry from `copied_globals` (already done), and
+ // delete the actual `R_*_COPY` relocation. Of course, we also need to re-apply relocations
+ // targeting this symbol, but we were going to do that at the end of this function anyway.
+ elf.shndx.rela_dyn.relaDeleteOne(elf, copied_global_kv.value.rela_index);
+ // TODO: once `MappedFile` has a way to delete a node (so it can re-use the space), we
+ // should delete `copied_global_kv.value.node`, which is an "orphaned" `copied_global` node.
+ } else {
+ _ = elf.want_copied_globals.swapRemove(global_name);
+ }
+
global_ptr.symtab_index.ptr(elf).node = new.node;
const old_head: String(.strtab) = old_head: {
@@ -1668,21 +1740,7 @@ fn setGlobalSymbolValue(
// 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);
- switch (elf.nodeWantsDsoRelocation(reloc.node)) {
- .no => unreachable, // there *was* a dynamic relocation!
- .yes => {},
- .yes_textrel => elf.textrel_count -= 1,
- }
- reloc.rela_index = .none;
- }
- ri = reloc.next;
- }
+ Symbol.Id.global(global_name).deleteDynamicTargetRelocs(elf);
}
// Finally, update the symbol value, re-applying target relocations. Also note that because we
@@ -2029,6 +2087,9 @@ const Symbol = struct {
};
}
+ /// Returns the value of this symbol, or 0 if it is undefined. If the symbol is an undefined
+ /// global for which we have emitted a copy relocation, returns the virtual address of that
+ /// copy relocation, which the symbol is guaranteed to resolve to at runtime.
fn value(s: Symbol.Id, elf: *Elf) u64 {
return switch (elf.symPtr(s.index(elf))) {
inline else => |sym| elf.targetLoad(&sym.value),
@@ -2084,12 +2145,36 @@ const Symbol = struct {
}
}
+ /// Scans through all relocations targeting `sym_id` and deletes each one's dynamic
+ /// relocation entry, if it has one.
+ ///
+ /// Asserts we are creating a DSO.
+ fn deleteDynamicTargetRelocs(sym_id: Symbol.Id, elf: *Elf) void {
+ assert(elf.ehdrField(.type) != .REL);
+ assert(elf.shndx.dynamic != .UNDEF);
+ var ri = sym_id.index(elf).ptr(elf).first_target_reloc;
+ while (ri != .none) {
+ const reloc = ri.get(elf);
+ assert(reloc.target == sym_id);
+ reloc.deleteOutputRel(elf);
+ ri = reloc.next;
+ }
+ }
+
/// 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 {
const node = s.index(elf).ptr(elf).node;
- if (node == .none) return false;
- return node.hasMoved(&elf.mf);
+ if (node != .none) {
+ return node.hasMoved(&elf.mf);
+ }
+ switch (s.unwrap()) {
+ .local => {},
+ .global => |name| if (elf.copied_globals.getPtr(name)) |copied_global| {
+ return copied_global.node.hasMoved(&elf.mf);
+ },
+ }
+ return false;
}
};
};
@@ -2110,6 +2195,7 @@ pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {
.segment,
.section,
.input_section,
+ .copied_global,
=> unreachable,
inline .nav,
@@ -2204,7 +2290,7 @@ pub fn addReloc(
) !void {
const node: MappedFile.Node.Index = Node.fromAtom(atom);
try elf.ensureUnusedRelocCapacity(node, 1);
- elf.addRelocAssumeCapacity(node, offset, .fromTypeErased(target), addend, @"type");
+ try elf.addRelocAssumeCapacity(node, offset, .fromTypeErased(target), addend, @"type");
}
pub fn navSymbol(elf: *Elf, nav_index: InternPool.Nav.Index) !link.File.SymbolId {
const zcu = elf.base.comp.zcu.?;
@@ -2252,7 +2338,7 @@ pub fn getVAddr(elf: *Elf, reloc_info: link.File.RelocInfo, target: link.File.Sy
const node: MappedFile.Node.Index = Node.fromAtom(reloc_info.parent.atom_index);
const target_sym: Symbol.Id = .fromTypeErased(target);
try elf.ensureUnusedRelocCapacity(node, 1);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
node,
reloc_info.offset,
target_sym,
@@ -2511,6 +2597,8 @@ fn create(
.strong_undef = .empty,
.weak_undef = .empty,
},
+ .copied_globals = .empty,
+ .want_copied_globals = .empty,
.node_global_symbols = .empty,
.dso_globals = .empty,
.shstrtab = .{ .map = .empty },
@@ -2558,6 +2646,8 @@ pub fn deinit(elf: *Elf) void {
elf.globals.weak_def.deinit(gpa);
elf.globals.strong_undef.deinit(gpa);
elf.globals.weak_undef.deinit(gpa);
+ elf.copied_globals.deinit(gpa);
+ elf.want_copied_globals.deinit(gpa);
elf.node_global_symbols.deinit(gpa);
elf.dso_globals.deinit(gpa);
elf.shstrtab.map.deinit(gpa);
@@ -3117,14 +3207,14 @@ fn initHeaders(
});
elf.plt_first_symbol_reloc = @enumFromInt(elf.symbol_relocs.items.len);
try elf.ensureUnusedRelocCapacity(plt_ni, 2);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
plt_ni,
2,
got_plt_sym,
8 * 1 - 4,
.{ .X86_64 = .PC32 },
);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
plt_ni,
8,
got_plt_sym,
@@ -3267,7 +3357,7 @@ pub fn endProgress(elf: *Elf) void {
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.
+/// Asserts that `ni` is a section, input section, copied global, NAV, UAV, or lazy code/data.
fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {
return switch (elf.getNode(ni)) {
.file => unreachable,
@@ -3278,6 +3368,7 @@ fn getNodeShndx(elf: *const Elf, ni: MappedFile.Node.Index) Section.Index {
.section => |shndx| shndx,
.input_section,
+ .copied_global,
.nav,
.uav,
.lazy_code,
@@ -3294,6 +3385,7 @@ fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
},
.section => |shndx| if (shndx == elf.shndx.tdata) 0 else shndx.vaddr(elf),
.input_section => unreachable,
+ .copied_global => unreachable,
inline .nav, .uav, .lazy_code, .lazy_const_data => |i| Symbol.Id.local(i.symbol(elf)).value(elf),
};
const offset, _ = ni.location(&elf.mf).resolve(&elf.mf);
@@ -3312,6 +3404,7 @@ fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void {
.shdr => unreachable, // cannot contain relocs
.segment => unreachable, // cannot contain relocs
.section => unreachable, // cannot contain relocs (.plt and .dynamic unsupported)
+ .copied_global => unreachable, // cannot contain relocs
.input_section => |isi| .{
&elf.input_sections.items[@intFromEnum(isi)].first_symbol_reloc,
&elf.input_sections.items[@intFromEnum(isi)].first_got_reloc,
@@ -4270,7 +4363,7 @@ fn loadObject(
.{rel.info.sym},
);
}
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
loc_node,
rel.offset - loc_sec.shdr.addr,
target,
@@ -4303,12 +4396,19 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
if (ehdr.machine != elf.ehdrField(.machine))
return diags.failParse(path, "bad machine", .{});
if (ehdr.shnum > 0) try fr.seekTo(ehdr.shoff);
+ // We're going to need to know the alignment of every section later.
+ const section_aligns = try gpa.alloc(std.mem.Alignment, ehdr.shnum);
+ defer gpa.free(section_aligns);
const dynamic_sh: ElfN.Shdr, const dynsym_sh: ElfN.Shdr = sh: {
var dynamic_sh: ?ElfN.Shdr = null;
var dynsym_sh: ?ElfN.Shdr = null;
- for (0..ehdr.shnum) |_| {
+ for (section_aligns) |*section_align| {
const sh = try r.peekStruct(ElfN.Shdr, target_endian);
try r.discardAll(ehdr.shentsize);
+ section_align.* = .fromByteUnits(std.math.ceilPowerOfTwoAssert(
+ usize,
+ @intCast(@max(sh.addralign, 1)),
+ ));
switch (sh.type) {
else => {},
.DYNAMIC => dynamic_sh = sh,
@@ -4395,17 +4495,51 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
}
if (sym.shndx == std.elf.SHN_UNDEF) continue;
+ if (sym.shndx >= ehdr.shnum) continue;
if (sym.name >= dynstr.len) {
return diags.failParse(path, "bad symbol name string", .{});
}
+ // We need to guess the worst-case alignment of the symbol. Yes, I know this seems
+ // insane---refer to the doc comment on `alignment` in `Elf.dso_globals`.
+ const sym_align: std.mem.Alignment = switch (sym.value) {
+ 0 => section_aligns[sym.shndx],
+ else => section_aligns[sym.shndx].min(@enumFromInt(@ctz(sym.value))),
+ };
+
const name = try elf.string(.strtab, std.mem.sliceTo(dynstr[sym.name..], 0));
const gop = elf.dso_globals.getOrPutAssumeCapacity(name);
- if (!gop.found_existing or gop.value_ptr.* == .NOTYPE) {
- gop.value_ptr.* = sym.info.type;
+
+ if (gop.found_existing and gop.value_ptr.type != .NOTYPE) {
+ if (sym.size > gop.value_ptr.size or
+ sym_align.compare(.gt, gop.value_ptr.alignment))
+ {
+ gop.value_ptr.size = @max(gop.value_ptr.size, sym.size);
+ gop.value_ptr.alignment = gop.value_ptr.alignment.max(sym_align);
+ if (elf.copied_globals.get(name)) |copied_global| {
+ // We have a copy relocation for this global, but the amount of space we
+ // reserved for it could be too small or underaligned!
+ try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size);
+ try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment);
+ const global_ptr = elf.globalByName(name).?;
+ switch (elf.symPtr(global_ptr.symtab_index)) {
+ inline else => |sym_ptr| elf.targetStore(&sym_ptr.size, @intCast(gop.value_ptr.size)),
+ }
+ switch (elf.dynsymPtr(global_ptr.dynsym_index)) {
+ inline else => |dynsym_ptr| elf.targetStore(&dynsym_ptr.size, @intCast(gop.value_ptr.size)),
+ }
+ }
+ }
+ continue;
}
+ gop.value_ptr.* = .{
+ .type = sym.info.type,
+ .size = sym.size,
+ .alignment = sym_align,
+ };
+
// If there's already an undefined symbol by this name of type STT_NOTYPE, populate
// its type now.
const global_ptr = elf.globals.strong_undef.getPtr(name) orelse
@@ -4414,14 +4548,20 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
if (global_ptr.dynsym_index == 0) continue;
+ if (elf.want_copied_globals.swapRemove(name)) {
+ // We just found a DSO definition of a symbol for which we wanted a copy
+ // relocation, so add one if we can!
+ _ = try elf.maybeAddCopyRelocation(name);
+ }
+
const sym_ptr = @field(elf.symPtr(global_ptr.symtab_index), @tagName(class));
+ errdefer comptime unreachable; // messing with the output file could invalidate `sym_ptr`
+
switch (elf.targetLoad(&sym_ptr.other).visibility) {
.HIDDEN, .INTERNAL, .PROTECTED => continue,
.DEFAULT => {},
}
- if (elf.targetLoad(&sym_ptr.shndx) != std.elf.SHN_UNDEF) continue;
-
const cur_info = elf.targetLoad(&sym_ptr.info);
if (cur_info.type == .NOTYPE) {
const new_type: std.elf.STT = switch (sym.info.type) {
@@ -4440,9 +4580,8 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
.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) {
+ // We turned STT_NOTYPE into STT_FUNC, so we now need a PLT entry...
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.
@@ -4630,6 +4769,9 @@ fn prelinkInner(elf: *Elf) !void {
}
break :rpath try elf.string(.dynstr, buf.items);
};
+ const soname: ?String(.dynstr) = if (elf.options.soname) |soname_slice| str: {
+ break :str try elf.string(.dynstr, soname_slice);
+ } else null;
const needed_len = elf.needed.count();
const dynamic_len = needed_len + @intFromBool(elf.options.soname != null) +
@intFromBool(rpath != .empty) +
@@ -4644,141 +4786,155 @@ fn prelinkInner(elf: *Elf) !void {
switch (elf.shdrPtr(elf.shndx.dynamic)) {
inline else => |shdr| elf.targetStore(&shdr.size, dynamic_size),
}
- const sec_dynamic = dynamic_ni.slice(&elf.mf);
- const dynamic_entries: [][2]ElfN.Addr = @ptrCast(@alignCast(sec_dynamic));
- var dynamic_index: usize = 0;
- for (
- dynamic_entries[dynamic_index..][0..needed_len],
- elf.needed.keys(),
- ) |*dynamic_entry, needed| dynamic_entry.* = .{ std.elf.DT_NEEDED, @intFromEnum(needed) };
- dynamic_index += needed_len;
- if (elf.options.soname) |soname| {
- dynamic_entries[dynamic_index] = .{ std.elf.DT_SONAME, @intFromEnum(try elf.string(.dynstr, soname)) };
- dynamic_index += 1;
- }
- if (rpath != .empty) {
- dynamic_entries[dynamic_index] = .{ std.elf.DT_RUNPATH, @intFromEnum(rpath) };
- dynamic_index += 1;
- }
- if (flags != 0) {
- dynamic_entries[dynamic_index] = .{ std.elf.DT_FLAGS, flags };
- dynamic_index += 1;
- }
- if (flags_1 != 0) {
- dynamic_entries[dynamic_index] = .{ std.elf.DT_FLAGS_1, flags_1 };
- dynamic_index += 1;
- }
- if (comp.config.output_mode == .Exe) {
- dynamic_entries[dynamic_index] = .{ std.elf.DT_DEBUG, 0 };
- dynamic_index += 1;
- }
- if (elf.shndx.init_array != .UNDEF) {
- dynamic_entries[dynamic_index..][0..2].* = .{
- .{ std.elf.DT_INIT_ARRAY, @intCast(elf.shndx.init_array.vaddr(elf)) },
- .{ std.elf.DT_INIT_ARRAYSZ, elf.targetLoad(
- &@field(elf.shdrPtr(elf.shndx.init_array), @tagName(ct_class)).size,
+
+ const dynamic_indices: struct {
+ init_array: ?usize,
+ fini_array: ?usize,
+ preinit_array: ?usize,
+ } = indices: {
+ const sec_dynamic = dynamic_ni.slice(&elf.mf);
+ const dynamic_entries: [][2]ElfN.Addr = @ptrCast(@alignCast(sec_dynamic));
+ errdefer comptime unreachable; // don't invalidate `dynamic_entries`
+ var dynamic_index: usize = 0;
+ for (
+ dynamic_entries[dynamic_index..][0..needed_len],
+ elf.needed.keys(),
+ ) |*dynamic_entry, needed| dynamic_entry.* = .{ std.elf.DT_NEEDED, @intFromEnum(needed) };
+ dynamic_index += needed_len;
+ if (soname) |soname_dynstr| {
+ dynamic_entries[dynamic_index] = .{ std.elf.DT_SONAME, @intFromEnum(soname_dynstr) };
+ dynamic_index += 1;
+ }
+ if (rpath != .empty) {
+ dynamic_entries[dynamic_index] = .{ std.elf.DT_RUNPATH, @intFromEnum(rpath) };
+ dynamic_index += 1;
+ }
+ if (flags != 0) {
+ dynamic_entries[dynamic_index] = .{ std.elf.DT_FLAGS, flags };
+ dynamic_index += 1;
+ }
+ if (flags_1 != 0) {
+ dynamic_entries[dynamic_index] = .{ std.elf.DT_FLAGS_1, flags_1 };
+ dynamic_index += 1;
+ }
+ if (comp.config.output_mode == .Exe) {
+ dynamic_entries[dynamic_index] = .{ std.elf.DT_DEBUG, 0 };
+ dynamic_index += 1;
+ }
+ const init_array_index: ?usize = if (elf.shndx.init_array != .UNDEF) i: {
+ dynamic_entries[dynamic_index..][0..2].* = .{
+ .{ std.elf.DT_INIT_ARRAY, @intCast(elf.shndx.init_array.vaddr(elf)) },
+ .{ std.elf.DT_INIT_ARRAYSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.init_array), @tagName(ct_class)).size,
+ ) },
+ };
+ defer dynamic_index += 2;
+ break :i dynamic_index;
+ } else null;
+ const fini_array_index: ?usize = if (elf.shndx.fini_array != .UNDEF) i: {
+ dynamic_entries[dynamic_index..][0..2].* = .{
+ .{ std.elf.DT_FINI_ARRAY, @intCast(elf.shndx.fini_array.vaddr(elf)) },
+ .{ std.elf.DT_FINI_ARRAYSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.fini_array), @tagName(ct_class)).size,
+ ) },
+ };
+ defer dynamic_index += 2;
+ break :i dynamic_index;
+ } else null;
+ const preinit_array_index: ?usize = if (elf.shndx.preinit_array != .UNDEF) i: {
+ dynamic_entries[dynamic_index..][0..2].* = .{
+ .{ std.elf.DT_PREINIT_ARRAY, @intCast(elf.shndx.preinit_array.vaddr(elf)) },
+ .{ std.elf.DT_PREINIT_ARRAYSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.preinit_array), @tagName(ct_class)).size,
+ ) },
+ };
+ defer dynamic_index += 2;
+ break :i dynamic_index;
+ } else null;
+ dynamic_entries[dynamic_index..][0..12].* = .{
+ .{ std.elf.DT_RELA, @intCast(elf.shndx.rela_dyn.vaddr(elf)) },
+ .{ std.elf.DT_RELASZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.rela_dyn), @tagName(ct_class)).size,
) },
- };
- try elf.ensureUnusedRelocCapacity(dynamic_ni, 1);
- elf.addRelocAssumeCapacity(
- dynamic_ni,
- @sizeOf(ElfN.Addr) * (2 * dynamic_index + 1),
- .local(elf.shndx.init_array.get(elf).lsi),
- 0,
- .absAddr(elf),
- );
- dynamic_index += 2;
- }
- if (elf.shndx.fini_array != .UNDEF) {
- dynamic_entries[dynamic_index..][0..2].* = .{
- .{ std.elf.DT_FINI_ARRAY, @intCast(elf.shndx.fini_array.vaddr(elf)) },
- .{ std.elf.DT_FINI_ARRAYSZ, elf.targetLoad(
- &@field(elf.shdrPtr(elf.shndx.fini_array), @tagName(ct_class)).size,
+ .{ std.elf.DT_RELAENT, @sizeOf(ElfN.Rela) },
+ .{ std.elf.DT_JMPREL, @intCast(elf.shndx.rela_plt.vaddr(elf)) },
+ .{ std.elf.DT_PLTRELSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.rela_plt), @tagName(ct_class)).size,
) },
- };
- try elf.ensureUnusedRelocCapacity(dynamic_ni, 1);
- elf.addRelocAssumeCapacity(
- dynamic_ni,
- @sizeOf(ElfN.Addr) * (2 * dynamic_index + 1),
- .local(elf.shndx.fini_array.get(elf).lsi),
- 0,
- .absAddr(elf),
- );
- dynamic_index += 2;
- }
- if (elf.shndx.preinit_array != .UNDEF) {
- dynamic_entries[dynamic_index..][0..2].* = .{
- .{ std.elf.DT_PREINIT_ARRAY, @intCast(elf.shndx.preinit_array.vaddr(elf)) },
- .{ std.elf.DT_PREINIT_ARRAYSZ, elf.targetLoad(
- &@field(elf.shdrPtr(elf.shndx.preinit_array), @tagName(ct_class)).size,
+ .{ std.elf.DT_PLTGOT, @intCast(elf.shndx.got_plt.vaddr(elf)) },
+ .{ std.elf.DT_PLTREL, std.elf.DT_RELA },
+ .{ std.elf.DT_SYMTAB, @intCast(elf.shndx.dynsym.vaddr(elf)) },
+ .{ std.elf.DT_SYMENT, @sizeOf(ElfN.Sym) },
+ .{ std.elf.DT_STRTAB, @intCast(elf.shndx.dynstr.vaddr(elf)) },
+ .{ std.elf.DT_STRSZ, elf.targetLoad(
+ &@field(elf.shdrPtr(elf.shndx.dynstr), @tagName(ct_class)).size,
) },
+ .{ std.elf.DT_NULL, 0 },
+ };
+ dynamic_index += 12;
+ assert(dynamic_index == dynamic_len);
+ if (elf.targetEndian() != native_endian) for (dynamic_entries) |*dynamic_entry|
+ std.mem.byteSwapAllFields(@TypeOf(dynamic_entry.*), dynamic_entry);
+
+ break :indices .{
+ .init_array = init_array_index,
+ .fini_array = fini_array_index,
+ .preinit_array = preinit_array_index,
};
- try elf.ensureUnusedRelocCapacity(dynamic_ni, 1);
- elf.addRelocAssumeCapacity(
- dynamic_ni,
- @sizeOf(ElfN.Addr) * (2 * dynamic_index + 1),
- .local(elf.shndx.preinit_array.get(elf).lsi),
- 0,
- .absAddr(elf),
- );
- dynamic_index += 2;
- }
- dynamic_entries[dynamic_index..][0..12].* = .{
- .{ std.elf.DT_RELA, @intCast(elf.shndx.rela_dyn.vaddr(elf)) },
- .{ std.elf.DT_RELASZ, elf.targetLoad(
- &@field(elf.shdrPtr(elf.shndx.rela_dyn), @tagName(ct_class)).size,
- ) },
- .{ std.elf.DT_RELAENT, @sizeOf(ElfN.Rela) },
- .{ std.elf.DT_JMPREL, @intCast(elf.shndx.rela_plt.vaddr(elf)) },
- .{ std.elf.DT_PLTRELSZ, elf.targetLoad(
- &@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 },
- .{ std.elf.DT_SYMTAB, @intCast(elf.shndx.dynsym.vaddr(elf)) },
- .{ std.elf.DT_SYMENT, @sizeOf(ElfN.Sym) },
- .{ std.elf.DT_STRTAB, @intCast(elf.shndx.dynstr.vaddr(elf)) },
- .{ std.elf.DT_STRSZ, elf.targetLoad(
- &@field(elf.shdrPtr(elf.shndx.dynstr), @tagName(ct_class)).size,
- ) },
- .{ std.elf.DT_NULL, 0 },
};
- dynamic_index += 12;
- assert(dynamic_index == dynamic_len);
- if (elf.targetEndian() != native_endian) for (dynamic_entries) |*dynamic_entry|
- std.mem.byteSwapAllFields(@TypeOf(dynamic_entry.*), dynamic_entry);
elf.dynamic_first_symbol_reloc = @enumFromInt(elf.symbol_relocs.items.len);
- try elf.ensureUnusedRelocCapacity(dynamic_ni, 5);
- elf.addRelocAssumeCapacity(
+ try elf.ensureUnusedRelocCapacity(dynamic_ni, 8);
+ if (dynamic_indices.init_array) |index| try elf.addRelocAssumeCapacity(
+ dynamic_ni,
+ @sizeOf(ElfN.Addr) * (2 * index + 1),
+ .local(elf.shndx.init_array.get(elf).lsi),
+ 0,
+ .absAddr(elf),
+ );
+ if (dynamic_indices.fini_array) |index| try elf.addRelocAssumeCapacity(
+ dynamic_ni,
+ @sizeOf(ElfN.Addr) * (2 * index + 1),
+ .local(elf.shndx.fini_array.get(elf).lsi),
+ 0,
+ .absAddr(elf),
+ );
+ if (dynamic_indices.preinit_array) |index| try elf.addRelocAssumeCapacity(
+ dynamic_ni,
+ @sizeOf(ElfN.Addr) * (2 * index + 1),
+ .local(elf.shndx.preinit_array.get(elf).lsi),
+ 0,
+ .absAddr(elf),
+ );
+ try elf.addRelocAssumeCapacity(
dynamic_ni,
@sizeOf(ElfN.Addr) * (2 * (dynamic_len - 12) + 1),
.local(elf.shndx.rela_dyn.get(elf).lsi),
0,
.absAddr(elf),
);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
dynamic_ni,
@sizeOf(ElfN.Addr) * (2 * (dynamic_len - 9) + 1),
.local(elf.shndx.rela_plt.get(elf).lsi),
0,
.absAddr(elf),
);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
dynamic_ni,
@sizeOf(ElfN.Addr) * (2 * (dynamic_len - 7) + 1),
.local(elf.shndx.got_plt.get(elf).lsi),
0,
.absAddr(elf),
);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
dynamic_ni,
@sizeOf(ElfN.Addr) * (2 * (dynamic_len - 5) + 1),
.local(elf.shndx.dynsym.get(elf).lsi),
0,
.absAddr(elf),
);
- elf.addRelocAssumeCapacity(
+ try elf.addRelocAssumeCapacity(
dynamic_ni,
@sizeOf(ElfN.Addr) * (2 * (dynamic_len - 3) + 1),
.local(elf.shndx.dynstr.get(elf).lsi),
@@ -4951,6 +5107,8 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize)
},
}
}
+/// Although this function requires a preceding call to `ensureUnusedRelocCapacity`, it is still
+/// fallible, because there are some rare cases for which we cannot reserve capacity upfront.
fn addRelocAssumeCapacity(
elf: *Elf,
node: MappedFile.Node.Index,
@@ -4958,7 +5116,7 @@ fn addRelocAssumeCapacity(
target: Symbol.Id,
addend: i64,
@"type": MachineRelocType,
-) void {
+) !void {
assert(node != .none);
switch (elf.ehdrField(.type)) {
.NONE, .CORE, _ => unreachable,
@@ -5021,25 +5179,25 @@ fn addRelocAssumeCapacity(
.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),
+ .@"64" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs64),
+ .@"32" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs32),
+ .@"32S" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs32s),
+ .PC64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel64),
+ .PC32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel32),
+ .PLT32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .pltrel32),
+ .SIZE64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .size64),
+ .SIZE32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .size32),
+ .DTPOFF64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .dtpoff64),
+ .DTPOFF32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .dtpoff32),
+ .TPOFF64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff64),
+ .TPOFF32 => try 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);
+ try 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);
+ try elf.addSymbolRelocAssumeCapacity(node, offset, got_sym, addend, .rel32);
},
// TODO: these are the address of an arbitrary symbol (or PLT entry) relative to the
@@ -5077,11 +5235,16 @@ fn addSymbolRelocAssumeCapacity(
target: Symbol.Id,
addend: i64,
@"type": SymbolReloc.Type,
-) void {
+) !void {
assert(elf.ehdrField(.type) != .REL);
const rela_index: Section.RelaIndex.Optional = r: {
if (elf.shndx.dynamic == .UNDEF) break :r .none;
+ const global_name = switch (target.unwrap()) {
+ .local => break :r .none,
+ .global => |name| name,
+ };
+
const rela_type: MachineRelocType = switch (elf.ehdrField(.machine)) {
else => |machine| @panic(@tagName(machine)),
.X86_64 => .{ .X86_64 = switch (@"type") {
@@ -5101,21 +5264,31 @@ fn addSymbolRelocAssumeCapacity(
.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,
- };
+ // TODO: even if the symbol is locally defined, preemption/interposition is a
+ // possibility, which this condition does not currently consider!
+ if (elf.globals.strong_def.contains(global_name) or
+ elf.globals.weak_def.contains(global_name))
+ {
+ break :r .none;
+ }
+
+ const dynsym_index = elf.globalByName(global_name).?.dynsym_index;
+ if (dynsym_index == 0) break :r .none;
switch (elf.nodeWantsDsoRelocation(node)) {
.no => break :r .none,
.yes => {},
- .yes_textrel => elf.textrel_count += 1,
+ .yes_textrel => if (try elf.maybeAddCopyRelocation(global_name)) {
+ // We were able to use a copy relocation on this symbol to avoid a text relocation,
+ // which is apparently considered a good thing despite copy relocations being an
+ // abomination. (This is necessary for correctness in some cases, because e.g. a
+ // 32-bit runtime relocation on a 64-bit target will often cause rtld errors due to
+ // the DSOs being loaded too far apart.)
+ break :r .none;
+ } else {
+ // At least for now, our only choice is a text relocation.
+ elf.textrel_count += 1;
+ },
}
// It currently looks like we need a runtime relocation for this.
@@ -5175,6 +5348,7 @@ fn addGotRelocAssumeCapacity(
.ehdr => unreachable, // cannot contain relocs
.shdr => unreachable, // cannot contain relocs
.segment => unreachable, // cannot contain relocs
+ .copied_global => unreachable, // cannot contain relocs
}
const gop = elf.got.getOrPutAssumeCapacity(target);
@@ -5275,29 +5449,37 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
.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|
- {
+ const need_reloc: bool = need_reloc: {
+ const global = g: {
+ if (elf.globals.strong_def.getPtr(name)) |g| break :g g;
+ if (elf.globals.weak_def.getPtr(name)) |g| break :g g;
+ // The global is undefined, which probably means we need a relocation---unless
+ // we have created a copy relocation for it, in which case we own the canonical
+ // address of this symbol in this DSO!
+ break :need_reloc !elf.copied_globals.contains(name);
+ };
+
// 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) };
+ break :need_reloc false;
}
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) };
- },
- }
+ break :need_reloc switch (visibility) {
+ .DEFAULT => true,
+ .INTERNAL, .HIDDEN, .PROTECTED => false,
+ };
+ };
+
+ if (!need_reloc) {
+ break :val .{ .unsigned = sym_id.value(elf) };
}
+
break :val .{ .reloc = .{
.type = if (tag == .symbol) .globDat(elf) else .dtpOffAddr(elf),
.dynsym_index = elf.globalByName(name).?.dynsym_index,
@@ -5403,6 +5585,80 @@ fn nodeWantsDsoRelocation(elf: *Elf, node: MappedFile.Node.Index) enum { yes, ye
return .yes;
}
+/// If the given undefined global could have a copy relocation, creates that relocation if it does
+/// not already exist, and returns `true`.
+///
+/// Returns `false` iff a copy relocation cannot currently be created for the global. If it may be
+/// possible in future, the symbol is added to `elf.want_copied_globals` so that the copy relocation
+/// will be created if and when we discover a suitable definition in an input DSO.
+///
+/// If this function creates a new copy relocation, it will also update relocations targeting the
+/// global where needed---the caller does not need to do this.
+///
+/// Asserts that `elf.shndx.dynamic != .UNDEF` and that `global_name` refers to an *undefined* global.
+fn maybeAddCopyRelocation(elf: *Elf, global_name: String(.strtab)) !bool {
+ assert(elf.shndx.dynamic != .UNDEF);
+
+ const gpa = elf.base.comp.gpa;
+
+ const global_ptr = elf.globals.strong_undef.getPtr(global_name) orelse
+ elf.globals.weak_undef.getPtr(global_name).?;
+
+ assert(global_ptr.dynsym_index != 0);
+
+ // Only dynamic executables may contain `R_*_COPY` relocations.
+ if (elf.shndx.dynamic == .UNDEF) return false;
+ if (elf.base.comp.config.output_mode != .Exe) return false;
+
+ const dso_global = elf.dso_globals.get(global_name) orelse {
+ // We do not have a definition to provide the correct size for the symbol. If a definition
+ // is discovered in a later DSO, we may at that point be able to add a copy relocation.
+ try elf.want_copied_globals.put(gpa, global_name, {});
+ return false;
+ };
+
+ if (dso_global.type != .OBJECT) return false;
+
+ const gop = try elf.copied_globals.getOrPut(gpa, global_name);
+ if (gop.found_existing) return true;
+ errdefer assert(elf.copied_globals.pop().?.key == global_name);
+
+ try elf.nodes.ensureUnusedCapacity(gpa, 1);
+ const node = try elf.mf.addLastChildNode(gpa, Section.Index.data.get(elf).ni, .{
+ .size = dso_global.size,
+ .alignment = dso_global.alignment,
+ });
+ errdefer comptime unreachable;
+
+ const vaddr = elf.computeNodeVAddr(node);
+ elf.nodes.appendAssumeCapacity(.{ .copied_global = global_name });
+ const rela_index = elf.shndx.rela_dyn.relaAddOneAssumeCapacity(elf, .{
+ .type = .copy(elf),
+ .offset = vaddr,
+ .raw_sym_index = global_ptr.dynsym_index,
+ .addend = 0,
+ });
+ gop.value_ptr.* = .{
+ .node = node,
+ .rela_index = rela_index,
+ };
+
+ switch (elf.symPtr(global_ptr.symtab_index)) {
+ inline else => |sym| elf.targetStore(&sym.size, @intCast(dso_global.size)),
+ }
+ switch (elf.dynsymPtr(global_ptr.dynsym_index)) {
+ inline else => |dynsym| elf.targetStore(&dynsym.size, @intCast(dso_global.size)),
+ }
+
+ // Because we now have a copy relocation, any dynamic relocations which target this symbol are
+ // now incorrect, since we now own the canonical address of the symbol. So delete those relocs
+ // and then update the symbol's address (and re-apply relocations targeting it of course).
+ Symbol.Id.global(global_name).deleteDynamicTargetRelocs(elf);
+ Symbol.Id.global(global_name).flushMoved(elf, vaddr);
+
+ return true;
+}
+
pub fn updateNav(elf: *Elf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
elf.updateNavInner(pt, nav_index) catch |err| switch (err) {
error.OutOfMemory,
@@ -5561,10 +5817,8 @@ pub fn flush(
error.Canceled => |e| return e,
else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}),
};
- const global = elf.globalByName(sym_name_strtab) orelse break :entry 0;
- switch (elf.symPtr(global.symtab_index)) {
- inline else => |sym| break :entry elf.targetLoad(&sym.value),
- }
+ if (elf.globalByName(sym_name_strtab) == null) break :entry 0;
+ break :entry Symbol.Id.global(sym_name_strtab).value(elf);
};
switch (elf.ehdrPtr()) {
inline else => |ehdr| elf.targetStore(&ehdr.entry, @intCast(entry_addr)),
@@ -5923,15 +6177,12 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
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),
- };
+ const old_sym_addr = Symbol.Id.global(name).value(elf);
Symbol.Id.global(name).flushMoved(
elf,
old_sym_addr - old_addr + addr,
);
- name = global.next_in_node;
+ name = elf.globalByName(name).?.next_in_node;
}
}
@@ -5968,18 +6219,20 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
var lsi, const end_lsi = ii.localSymbolRange(elf);
while (lsi != end_lsi) : (lsi = @enumFromInt(@intFromEnum(lsi) + 1)) {
if (lsi.index().ptr(elf).node != ni) continue;
- const old_sym_addr: u64 = switch (elf.symPtr(lsi.index())) {
- inline else => |sym| switch (elf.targetLoad(&sym.other).visibility) {
- .HIDDEN, .INTERNAL => {
- // This is actually a global symbol which got demoted to STB_LOCAL due
- // to its visibility. It will be handled in the global symbols pass
- // below; don't touch it now.
- continue;
- },
- .PROTECTED => unreachable, // not allowed for an STB_LOCAL symbol
- .DEFAULT => elf.targetLoad(&sym.value),
- },
+ const visibility: std.elf.STV = switch (elf.symPtr(lsi.index())) {
+ inline else => |sym| elf.targetLoad(&sym.other).visibility,
};
+ switch (visibility) {
+ .HIDDEN, .INTERNAL => {
+ // This is actually a global symbol which got demoted to STB_LOCAL due
+ // to its visibility. It will be handled in the global symbols pass
+ // below; don't touch it now.
+ continue;
+ },
+ .PROTECTED => unreachable, // not allowed for an STB_LOCAL symbol
+ .DEFAULT => {},
+ }
+ const old_sym_addr = Symbol.Id.local(lsi).value(elf);
Symbol.Id.local(lsi).flushMoved(
elf,
old_sym_addr - old_section_addr + new_section_addr,
@@ -5991,15 +6244,12 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
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),
- };
+ const old_sym_addr = Symbol.Id.global(name).value(elf);
Symbol.Id.global(name).flushMoved(
elf,
old_sym_addr - old_section_addr + new_section_addr,
);
- name = global.next_in_node;
+ name = elf.globalByName(name).?.next_in_node;
}
}
@@ -6010,6 +6260,19 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
isi.ptrConst(elf).first_got_reloc,
);
},
+ .copied_global => |global_name| {
+ const copied_global = elf.copied_globals.getPtr(global_name) orelse {
+ // TODO: this node is orphaned, which is possible because `MappedFile` does not yet
+ // support deleting nodes. See logic in `setGlobalSymbolValue`.
+ return;
+ };
+ assert(copied_global.node == ni);
+
+ const new_addr = elf.computeNodeVAddr(ni);
+ elf.shndx.rela_dyn.relaSetOffset(elf, copied_global.rela_index, new_addr);
+
+ Symbol.Id.global(global_name).flushMoved(elf, new_addr);
+ },
inline .nav, .uav, .lazy_code, .lazy_const_data => |mi| {
const new_addr = elf.computeNodeVAddr(ni);
Symbol.Id.local(mi.symbol(elf)).flushMoved(elf, new_addr);
@@ -6137,7 +6400,7 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void {
}
},
},
- .input_section, .nav, .uav, .lazy_code, .lazy_const_data => {},
+ .copied_global, .input_section, .nav, .uav, .lazy_code, .lazy_const_data => {},
}
}
fn updateDynamicEntry(elf: *Elf, key: u32, new_val: u64) void {
@@ -6262,9 +6525,9 @@ fn updateExportsInner(
.uav => |uav| .{ (try elf.uavMapIndex(uav, .none)).symbol(elf), .OBJECT },
};
while (try elf.idle(pt.tid)) {}
- const value: u64, const size: u64, const shndx: Section.Index = switch (elf.symPtr(exported_lsi.index())) {
+ const value: u64 = Symbol.Id.local(exported_lsi).value(elf);
+ const size: u64, const shndx: Section.Index = switch (elf.symPtr(exported_lsi.index())) {
inline else => |exported_sym| .{
- elf.targetLoad(&exported_sym.value),
elf.targetLoad(&exported_sym.size),
.fromSection(elf.targetLoad(&exported_sym.shndx)),
},
@@ -6368,6 +6631,7 @@ pub fn printNode(
elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf),
});
},
+ .copied_global => |name| try w.print("(copy:{s})", .{name}),
.nav => |nmi| {
const zcu = elf.base.comp.zcu.?;
const ip = &zcu.intern_pool;