authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-07-04 13:39:06+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-07-07 13:05:31+01:00
log1d6305631e134696a57d93575f97ab3d8c1fe5cf
tree584f550d762c81a52df9fbf8634678366e546c7d
parent3d988929d4bc6623106d8156e37c43ad22cb2908
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: handle symbol preemption properly


1 files changed, 46 insertions(+), 110 deletions(-)

src/link/Elf2.zig+46-110
......@@ -479,9 +479,8 @@ const Section = struct {
479479 }
480480
481481 fn vaddr(s: Index, elf: *Elf) u64 {
482 return switch (s.get(elf).lsi) {
483 .null => 0,
484 else => |lsi| Symbol.Id.local(lsi).value(elf),
482 return switch (elf.shdrPtr(s)) {
483 inline else => |shdr| elf.targetLoad(&shdr.addr),
485484 };
486485 }
487486
......@@ -1730,16 +1729,12 @@ fn addGlobalSymbolAssumeCapacity(elf: *Elf, opts: AddGlobalSymbolOptions) error{
17301729 elf.moveDemotedGlobal(new_global_ptr);
17311730 }
17321731
1733 if (new_global_ptr.dynsym_index != 0 and
1734 opts.visibility == .DEFAULT and
1735 opts.shndx == .UNDEF and
1736 (@"type" == .FUNC or @"type" == std.elf.STT.GNU_IFUNC))
1737 {
1738 // We're adding an undefined global STT_FUNC symbol which could be resolved by another DSO.
1739 // We therefore might need a PLT entry, so let's add one now.
1740 elf.addPltEntry(opts.name.strtab, new_global_ptr.dynsym_index);
1741 // TODO: we also need to emit a PLT entry if the symbol could be preempted/interposed! By
1742 // not doing that we're basically implementing the behavior of `-Bsymbolic-functions`.
1732 switch (@"type") {
1733 .FUNC, .GNU_IFUNC => if (!elf.haveCanonicalSymbolDefinition(.global(opts.name.strtab))) {
1734 // This STT_FUNC symbol might be defined externally, so it needs a PLT entry.
1735 elf.addPltEntry(opts.name.strtab, new_global_ptr.dynsym_index);
1736 },
1737 else => {},
17431738 }
17441739
17451740 return .global(opts.name.strtab);
......@@ -1852,9 +1847,7 @@ fn setGlobalSymbolValue(
18521847 // delete its newly-unnecessary runtime relocation to avoid a runtime dynamic linker error.
18531848 // This also allows the PLT entry to be reused---see `pltEntryIsDead`.
18541849 if (elf.plt.getIndex(global_name)) |plt_index| {
1855 // TODO: we might still need the PLT entry if the symbol could be preempted/interposed! See
1856 // matching comment at the end of `addGlobalSymbolAssumeCapacity`.
1857 if (!elf.pltEntryIsDead(plt_index)) {
1850 if (elf.haveCanonicalSymbolDefinition(.global(global_name)) and !elf.pltEntryIsDead(plt_index)) {
18581851 elf.shndx.rela_plt.relaDeleteOne(elf, @enumFromInt(plt_index));
18591852 assert(elf.pltEntryIsDead(plt_index));
18601853 }
......@@ -2372,6 +2365,32 @@ fn globalByName(elf: *const Elf, name: String(.strtab)) ?*Symbol.Global {
23722365 return null;
23732366}
23742367
2368fn haveCanonicalSymbolDefinition(elf: *Elf, sym: Symbol.Id) bool {
2369 const global_name = switch (sym.unwrap()) {
2370 .local => return true,
2371 .global => |name| name,
2372 };
2373
2374 if (elf.shndx.dynamic == .UNDEF) return true;
2375
2376 const global_ptr = elf.globals.strong_def.getPtr(global_name) orelse
2377 elf.globals.weak_def.getPtr(global_name) orelse
2378 return false; // no definition at all
2379
2380 if (elf.base.comp.config.output_mode == .Exe) {
2381 // Symbols defined in executables cannot be preempted
2382 return true;
2383 }
2384
2385 const visibility: std.elf.STV = switch (elf.symPtr(global_ptr.symtab_index)) {
2386 inline else => |sym_ptr| elf.targetLoad(&sym_ptr.other).visibility,
2387 };
2388 return switch (visibility) {
2389 .INTERNAL, .HIDDEN, .PROTECTED => true, // protection prevents preemption
2390 .DEFAULT => false,
2391 };
2392}
2393
23752394pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {
23762395 const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) {
23772396 .file,
......@@ -5785,11 +5804,9 @@ fn addSymbolRelocAssumeCapacity(
57855804 assert(elf.ehdrField(.type) != .REL);
57865805
57875806 const rela_index: Section.RelaIndex.Optional = r: {
5788 if (elf.shndx.dynamic == .UNDEF) break :r .none;
5789 const global_name = switch (target.unwrap()) {
5790 .local => break :r .none,
5791 .global => |name| name,
5792 };
5807 if (elf.haveCanonicalSymbolDefinition(target)) break :r .none;
5808 // If the definition is (potentially) external, `target` must be global.
5809 const global_name = target.unwrap().global;
57935810
57945811 const rela_type: MachineRelocType = switch (elf.ehdrField(.machine)) {
57955812 else => |machine| @panic(@tagName(machine)),
......@@ -5850,16 +5867,9 @@ fn addSymbolRelocAssumeCapacity(
58505867 },
58515868 },
58525869 };
5853 // TODO: even if the symbol is locally defined, preemption/interposition is a
5854 // possibility, which this condition does not currently consider!
5855 if (elf.globals.strong_def.contains(global_name) or
5856 elf.globals.weak_def.contains(global_name))
5857 {
5858 break :r .none;
5859 }
58605870
58615871 const dynsym_index = elf.globalByName(global_name).?.dynsym_index;
5862 if (dynsym_index == 0) break :r .none;
5872 assert(dynsym_index != 0);
58635873
58645874 switch (elf.nodeWantsDsoRelocation(node)) {
58655875 .no => break :r .none,
......@@ -5986,25 +5996,8 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
59865996 } = switch (elf.got.keys()[got_index]) {
59875997 .reserved => .{ .unsigned = 0 },
59885998 .tpoff => |sym_id| val: {
5989 // We will break from this block if we require a relocation.
5990 known: {
5991 if (elf.base.comp.config.output_mode != .Exe) {
5992 // Only the executable's per-module TLS block is at a known offset from the
5993 // general TLS pointer.
5994 break :known;
5995 }
5996 switch (sym_id.unwrap()) {
5997 .local => {},
5998 .global => |name| if (elf.globals.strong_undef.contains(name) or
5999 elf.globals.weak_undef.contains(name))
6000 {
6001 // This is an external TLS symbol, so we don't know its offset.
6002 break :known;
6003 },
6004 }
6005 // It's a symbol which we define, the symbol is not interposable because we're the
6006 // executable, and we know our per-module TLS block's offset because we're the
6007 // executable. We therefore know this value!
5999 // Only the executable's per-module TLS block is at a known offset from the TLS pointer.
6000 if (elf.base.comp.config.output_mode == .Exe and elf.haveCanonicalSymbolDefinition(sym_id)) {
60086001 const tls_phndx = elf.getNode(elf.ni.tls).segment;
60096002 const tls_size: u64 = switch (elf.phdrSlice()) {
60106003 inline else => |phdr| tls_size: {
......@@ -6036,46 +6029,13 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
60366029 } },
60376030 };
60386031 },
6039 .symbol, .tlsgd1 => |sym_id, tag| val: {
6040 const name = switch (sym_id.unwrap()) {
6041 .local => break :val .{ .unsigned = sym_id.value(elf) },
6042 .global => |name| name,
6043 };
6044 // If the symbol is *defined* in this module, we might be able to avoid the relocation.
6045 const need_reloc: bool = need_reloc: {
6046 const global = g: {
6047 if (elf.globals.strong_def.getPtr(name)) |g| break :g g;
6048 if (elf.globals.weak_def.getPtr(name)) |g| break :g g;
6049 // The global is undefined, which probably means we need a relocation---unless
6050 // we have created a copy relocation for it, in which case we own the canonical
6051 // address of this symbol in this DSO!
6052 break :need_reloc !elf.copied_globals.contains(name);
6053 };
6054
6055 // We have a definition, but it might be interposable (aka preemptible). There
6056 // are two cases where it is not and so we can (and, in fact, must) elide the
6057 // runtime relocation:
6058 // * We are the executable. Symbols from executables cannot be interposed.
6059 // * The symbol's visibility disallows interposition.
6060 if (elf.base.comp.config.output_mode == .Exe) {
6061 break :need_reloc false;
6062 }
6063 const visibility: std.elf.STV = switch (elf.symPtr(global.symtab_index)) {
6064 inline else => |sym| elf.targetLoad(&sym.other).visibility,
6065 };
6066 break :need_reloc switch (visibility) {
6067 .DEFAULT => true,
6068 .INTERNAL, .HIDDEN, .PROTECTED => false,
6069 };
6070 };
6071
6072 if (!need_reloc) {
6073 break :val .{ .unsigned = sym_id.value(elf) };
6032 .symbol, .tlsgd1 => |sym, tag| val: {
6033 if (elf.haveCanonicalSymbolDefinition(sym)) {
6034 break :val .{ .unsigned = sym.value(elf) };
60746035 }
6075
60766036 break :val .{ .reloc = .{
60776037 .type = if (tag == .symbol) .globDat(elf) else .dtpOffAddr(elf),
6078 .dynsym_index = elf.globalByName(name).?.dynsym_index,
6038 .dynsym_index = elf.globalByName(sym.unwrap().global).?.dynsym_index,
60796039 .addend = 0,
60806040 } };
60816041 },
......@@ -6088,31 +6048,7 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
60886048 .X86_64 => .{ .X86_64 = .DTPMOD64 },
60896049 .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_DTPMOD64 else .TLS_DTPMOD32 },
60906050 },
6091 .dynsym_index = switch (sym.unwrap()) {
6092 .local => 0,
6093 .global => |name| dsi: {
6094 // Like in the `.tlsgd1` case, we need to check for a non-interposable definition.
6095 if (elf.globals.strong_def.getPtr(name) orelse
6096 elf.globals.weak_def.getPtr(name)) |global|
6097 {
6098 if (elf.base.comp.config.output_mode == .Exe) {
6099 break :dsi 0; // non-interposable definition
6100 }
6101 const visibility: std.elf.STV = switch (elf.symPtr(global.symtab_index)) {
6102 inline else => |sym_ptr| elf.targetLoad(&sym_ptr.other).visibility,
6103 };
6104 switch (visibility) {
6105 .DEFAULT => {},
6106 .INTERNAL, .HIDDEN, .PROTECTED => {
6107 break :dsi 0; // non-interposable definition
6108 },
6109 }
6110 }
6111 // `sym` is either undefined or an interposable definition, so use its
6112 // actual dynsym index.
6113 break :dsi elf.globalByName(name).?.dynsym_index;
6114 },
6115 },
6051 .dynsym_index = if (elf.haveCanonicalSymbolDefinition(sym)) 0 else elf.globalByName(sym.unwrap().global).?.dynsym_index,
61166052 .addend = 0,
61176053 },
61186054 },