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 {...@@ -479,9 +479,8 @@ const Section = struct {
479 }479 }
480480
481 fn vaddr(s: Index, elf: *Elf) u64 {481 fn vaddr(s: Index, elf: *Elf) u64 {
482 return switch (s.get(elf).lsi) {482 return switch (elf.shdrPtr(s)) {
483 .null => 0,483 inline else => |shdr| elf.targetLoad(&shdr.addr),
484 else => |lsi| Symbol.Id.local(lsi).value(elf),
485 };484 };
486 }485 }
487486
...@@ -1730,16 +1729,12 @@ fn addGlobalSymbolAssumeCapacity(elf: *Elf, opts: AddGlobalSymbolOptions) error{...@@ -1730,16 +1729,12 @@ fn addGlobalSymbolAssumeCapacity(elf: *Elf, opts: AddGlobalSymbolOptions) error{
1730 elf.moveDemotedGlobal(new_global_ptr);1729 elf.moveDemotedGlobal(new_global_ptr);
1731 }1730 }
17321731
1733 if (new_global_ptr.dynsym_index != 0 and1732 switch (@"type") {
1734 opts.visibility == .DEFAULT and1733 .FUNC, .GNU_IFUNC => if (!elf.haveCanonicalSymbolDefinition(.global(opts.name.strtab))) {
1735 opts.shndx == .UNDEF and1734 // This STT_FUNC symbol might be defined externally, so it needs a PLT entry.
1736 (@"type" == .FUNC or @"type" == std.elf.STT.GNU_IFUNC))1735 elf.addPltEntry(opts.name.strtab, new_global_ptr.dynsym_index);
1737 {1736 },
1738 // We're adding an undefined global STT_FUNC symbol which could be resolved by another DSO.1737 else => {},
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`.
1743 }1738 }
17441739
1745 return .global(opts.name.strtab);1740 return .global(opts.name.strtab);
...@@ -1852,9 +1847,7 @@ fn setGlobalSymbolValue(...@@ -1852,9 +1847,7 @@ fn setGlobalSymbolValue(
1852 // delete its newly-unnecessary runtime relocation to avoid a runtime dynamic linker error.1847 // delete its newly-unnecessary runtime relocation to avoid a runtime dynamic linker error.
1853 // This also allows the PLT entry to be reused---see `pltEntryIsDead`.1848 // This also allows the PLT entry to be reused---see `pltEntryIsDead`.
1854 if (elf.plt.getIndex(global_name)) |plt_index| {1849 if (elf.plt.getIndex(global_name)) |plt_index| {
1855 // TODO: we might still need the PLT entry if the symbol could be preempted/interposed! See1850 if (elf.haveCanonicalSymbolDefinition(.global(global_name)) and !elf.pltEntryIsDead(plt_index)) {
1856 // matching comment at the end of `addGlobalSymbolAssumeCapacity`.
1857 if (!elf.pltEntryIsDead(plt_index)) {
1858 elf.shndx.rela_plt.relaDeleteOne(elf, @enumFromInt(plt_index));1851 elf.shndx.rela_plt.relaDeleteOne(elf, @enumFromInt(plt_index));
1859 assert(elf.pltEntryIsDead(plt_index));1852 assert(elf.pltEntryIsDead(plt_index));
1860 }1853 }
...@@ -2372,6 +2365,32 @@ fn globalByName(elf: *const Elf, name: String(.strtab)) ?*Symbol.Global {...@@ -2372,6 +2365,32 @@ fn globalByName(elf: *const Elf, name: String(.strtab)) ?*Symbol.Global {
2372 return null;2365 return null;
2373}2366}
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
2375pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {2394pub fn symbolForAtom(elf: *Elf, atom: link.File.AtomId) link.File.SymbolId {
2376 const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) {2395 const lsi: Symbol.LocalIndex = switch (elf.getNode(Node.fromAtom(atom))) {
2377 .file,2396 .file,
...@@ -5785,11 +5804,9 @@ fn addSymbolRelocAssumeCapacity(...@@ -5785,11 +5804,9 @@ fn addSymbolRelocAssumeCapacity(
5785 assert(elf.ehdrField(.type) != .REL);5804 assert(elf.ehdrField(.type) != .REL);
57865805
5787 const rela_index: Section.RelaIndex.Optional = r: {5806 const rela_index: Section.RelaIndex.Optional = r: {
5788 if (elf.shndx.dynamic == .UNDEF) break :r .none;5807 if (elf.haveCanonicalSymbolDefinition(target)) break :r .none;
5789 const global_name = switch (target.unwrap()) {5808 // If the definition is (potentially) external, `target` must be global.
5790 .local => break :r .none,5809 const global_name = target.unwrap().global;
5791 .global => |name| name,
5792 };
57935810
5794 const rela_type: MachineRelocType = switch (elf.ehdrField(.machine)) {5811 const rela_type: MachineRelocType = switch (elf.ehdrField(.machine)) {
5795 else => |machine| @panic(@tagName(machine)),5812 else => |machine| @panic(@tagName(machine)),
...@@ -5850,16 +5867,9 @@ fn addSymbolRelocAssumeCapacity(...@@ -5850,16 +5867,9 @@ fn addSymbolRelocAssumeCapacity(
5850 },5867 },
5851 },5868 },
5852 };5869 };
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
5861 const dynsym_index = elf.globalByName(global_name).?.dynsym_index;5871 const dynsym_index = elf.globalByName(global_name).?.dynsym_index;
5862 if (dynsym_index == 0) break :r .none;5872 assert(dynsym_index != 0);
58635873
5864 switch (elf.nodeWantsDsoRelocation(node)) {5874 switch (elf.nodeWantsDsoRelocation(node)) {
5865 .no => break :r .none,5875 .no => break :r .none,
...@@ -5986,25 +5996,8 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {...@@ -5986,25 +5996,8 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
5986 } = switch (elf.got.keys()[got_index]) {5996 } = switch (elf.got.keys()[got_index]) {
5987 .reserved => .{ .unsigned = 0 },5997 .reserved => .{ .unsigned = 0 },
5988 .tpoff => |sym_id| val: {5998 .tpoff => |sym_id| val: {
5989 // We will break from this block if we require a relocation.5999 // Only the executable's per-module TLS block is at a known offset from the TLS pointer.
5990 known: {6000 if (elf.base.comp.config.output_mode == .Exe and elf.haveCanonicalSymbolDefinition(sym_id)) {
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!
6008 const tls_phndx = elf.getNode(elf.ni.tls).segment;6001 const tls_phndx = elf.getNode(elf.ni.tls).segment;
6009 const tls_size: u64 = switch (elf.phdrSlice()) {6002 const tls_size: u64 = switch (elf.phdrSlice()) {
6010 inline else => |phdr| tls_size: {6003 inline else => |phdr| tls_size: {
...@@ -6036,46 +6029,13 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {...@@ -6036,46 +6029,13 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
6036 } },6029 } },
6037 };6030 };
6038 },6031 },
6039 .symbol, .tlsgd1 => |sym_id, tag| val: {6032 .symbol, .tlsgd1 => |sym, tag| val: {
6040 const name = switch (sym_id.unwrap()) {6033 if (elf.haveCanonicalSymbolDefinition(sym)) {
6041 .local => break :val .{ .unsigned = sym_id.value(elf) },6034 break :val .{ .unsigned = sym.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) };
6074 }6035 }
6075
6076 break :val .{ .reloc = .{6036 break :val .{ .reloc = .{
6077 .type = if (tag == .symbol) .globDat(elf) else .dtpOffAddr(elf),6037 .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,
6079 .addend = 0,6039 .addend = 0,
6080 } };6040 } };
6081 },6041 },
...@@ -6088,31 +6048,7 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {...@@ -6088,31 +6048,7 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
6088 .X86_64 => .{ .X86_64 = .DTPMOD64 },6048 .X86_64 => .{ .X86_64 = .DTPMOD64 },
6089 .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_DTPMOD64 else .TLS_DTPMOD32 },6049 .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_DTPMOD64 else .TLS_DTPMOD32 },
6090 },6050 },
6091 .dynsym_index = switch (sym.unwrap()) {6051 .dynsym_index = if (elf.haveCanonicalSymbolDefinition(sym)) 0 else elf.globalByName(sym.unwrap().global).?.dynsym_index,
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 },
6116 .addend = 0,6052 .addend = 0,
6117 },6053 },
6118 },6054 },