authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-07-02 13:43:56+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-07-07 13:05:31+01:00
log3d988929d4bc6623106d8156e37c43ad22cb2908
treee98d4ba07bc73aa4d6c0b1cedf76137982592a88
parent1ef976880aa7a0be3a8c28ade99055d73ff6f2ac
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: don't put dummy entries in dynsym

To do this, Elf2 had to learn to deal with a symbol's dynsym index changing, so that we can move a symbol in dynsym to fill the gap. Luckily it turns out that's pretty straightforward---much like when a symtab index changes in a relocatable, we just need to update any output relocation entries targeting that symbol. It's just a little trickier since there are a few different places those relocations could come from: they could just correspond to symbol relocations, but they could also be copy relocations, PLT relocations, or GOT relocations.

1 files changed, 141 insertions(+), 76 deletions(-)

src/link/Elf2.zig+141-76
......@@ -154,6 +154,9 @@ tls_size_symbol_relocs: std.array_hash_map.Auto(SymbolReloc.Index, void),
154154section_by_name: std.array_hash_map.Auto(String(.shstrtab), void),
155155/// Key is the name of a global symbol which has been moved to a new symtab index. Any relocation
156156/// entries which target that symbol must be updated to reference the correct symbol index.
157///
158/// When emitting a relocatable (`ET_REL`), this refers to the index in `.symtab`. Otherwise, it
159/// refers to the index in `.dynsym`.
157160changed_symtab_index: std.array_hash_map.Auto(String(.strtab), void),
158161/// Counts how many relocations are currently in `.rela.dyn` which would require a `DT_TEXTREL`
159162/// entry in the `.dynamic` section. This allows adding `DT_TEXTREL` to the output `.dynamic`
......@@ -1331,12 +1334,9 @@ fn ensureUnusedSymbolCapacity(elf: *Elf, len: u32, kind: enum { all_local, maybe
13311334 try elf.symtab.ensureUnusedCapacity(gpa, len);
13321335
13331336 // If adding locals, we may need to move one global out of the way for each local. If adding
1334 // globals, they could all get demoted to STB_LOCAL, which would mean we move those N globals
1335 // *and* we move up to N other globals out of their way.
1336 try elf.changed_symtab_index.ensureUnusedCapacity(gpa, switch (kind) {
1337 .all_local => len,
1338 .maybe_global => len * 2,
1339 });
1337 // globals, they could all get demoted to STB_LOCAL, meaning we have to move N other globals
1338 // around to keep `.dynsym` compact. Either way, the maximum is N.
1339 try elf.changed_symtab_index.ensureUnusedCapacity(gpa, len);
13401340
13411341 {
13421342 // Ensure the symtab section's node is big enough
......@@ -1468,7 +1468,7 @@ fn addLocalSymbolAssumeCapacity(elf: *Elf, opts: AddLocalSymbolOptions) Symbol.L
14681468 const global_name: String(.strtab) = @enumFromInt(elf.targetLoad(&new_sym.name));
14691469 elf.globalByName(global_name).?.symtab_index = new_index;
14701470
1471 if (target_index.ptr(elf).first_target_reloc != .none) {
1471 if (elf.ehdrField(.type) == .REL and target_index.ptr(elf).first_target_reloc != .none) {
14721472 // This symbol's index is changing, so queue an update of relocs targeting it.
14731473 elf.changed_symtab_index.putAssumeCapacity(global_name, {});
14741474 }
......@@ -1950,61 +1950,68 @@ fn moveDemotedGlobal(elf: *Elf, global_ptr: *Symbol.Global) void {
19501950
19511951 elf.targetStore(&shdr.info, @intFromEnum(dest_index) + 1);
19521952
1953 if (src_index == dest_index) {
1954 // The demoted global was already the first global, so we don't need to do any swap.
1955 return;
1956 }
1953 if (src_index != dest_index) {
1954 // The demoted global was not the first global in the symtab, so we need to swap it
1955 // to its new location.
19571956
1958 const src_sym_ptr = @field(elf.symPtr(src_index), @tagName(class));
1959 const dest_sym_ptr = @field(elf.symPtr(dest_index), @tagName(class));
1957 const src_sym_ptr = @field(elf.symPtr(src_index), @tagName(class));
1958 const dest_sym_ptr = @field(elf.symPtr(dest_index), @tagName(class));
19601959
1961 const this_name: String(.strtab) = @enumFromInt(elf.targetLoad(&src_sym_ptr.name));
1962 assert(elf.globalByName(this_name).? == global_ptr);
1963 if (global_ptr.symtab_index.ptr(elf).first_target_reloc != .none) {
1964 // This symbol's index is changing, so queue an update of relocs targeting it.
1965 elf.changed_symtab_index.putAssumeCapacity(this_name, {});
1966 }
1960 const this_name: String(.strtab) = @enumFromInt(elf.targetLoad(&src_sym_ptr.name));
1961 assert(elf.globalByName(this_name).? == global_ptr);
1962
1963 const other_name: String(.strtab) = @enumFromInt(elf.targetLoad(&dest_sym_ptr.name));
1964 const other_global_ptr = elf.globalByName(other_name).?;
1965 assert(other_global_ptr.symtab_index == dest_index);
19671966
1968 const other_name: String(.strtab) = @enumFromInt(elf.targetLoad(&dest_sym_ptr.name));
1969 const other_global_ptr = elf.globalByName(other_name).?;
1970 assert(other_global_ptr.symtab_index == dest_index);
1971 if (other_global_ptr.symtab_index.ptr(elf).first_target_reloc != .none) {
1972 // This other symbol's index is changing, so queue an update of relocs targeting it.
1973 elf.changed_symtab_index.putAssumeCapacity(other_name, {});
1967 // First swap the symtab entries...
1968 std.mem.swap(class.ElfN().Sym, src_sym_ptr, dest_sym_ptr);
1969 // ...then the `elf.symtab` metadata...
1970 std.mem.swap(Symbol, src_index.ptr(elf), dest_index.ptr(elf));
1971 // ...then update the `elf.globals` tracking.
1972 global_ptr.symtab_index = dest_index;
1973 other_global_ptr.symtab_index = src_index;
19741974 }
19751975
1976 // First swap the symtab entries...
1977 std.mem.swap(class.ElfN().Sym, src_sym_ptr, dest_sym_ptr);
1978 // ...then the `elf.symtab` metadata...
1979 std.mem.swap(Symbol, src_index.ptr(elf), dest_index.ptr(elf));
1980 // ...then update the `elf.globals` tracking.
1981 global_ptr.symtab_index = dest_index;
1982 other_global_ptr.symtab_index = src_index;
1983
1984 // We also need to get rid of the dynsym entry if there is one. For simplicity, just
1985 // replace it with a dummy entry which will never be used and will not cause problems.
1986 // TODO: we should have a free-list of dynsym slots so that other symbols can go here.
1987 // TODO: it would also be best to just avoid having gaps in the dynsym altogether.
1976 // We also need to get rid of the dynsym entry if there is one. To keep dynsym compact,
1977 // we'll move another symbol into its place just like we did above.
19881978 if (global_ptr.dynsym_index != 0) {
1989 const dynsym = @field(elf.dynsymPtr(global_ptr.dynsym_index), @tagName(class));
1990 dynsym.* = .{
1991 .name = @intFromEnum(String(.dynstr).empty),
1992 .value = 0,
1993 .size = 0,
1994 .info = .{
1995 .type = .NOTYPE,
1996 // STB_WEAK is important: we mustn't cause a dynamic linker error if the
1997 // symbol can't be resolved.
1998 .bind = .WEAK,
1999 },
2000 // SHN_UNDEF is important: we mustn't define this symbol for other DSOs.
2001 .shndx = std.elf.SHN_UNDEF,
2002 .other = .{ .visibility = .DEFAULT },
2003 };
2004 if (elf.targetEndian() != native_endian) {
2005 std.mem.byteSwapAllFields(class.ElfN().Sym, dynsym);
2006 }
1979 const dynsym_shdr = @field(elf.shdrPtr(elf.shndx.dynsym), @tagName(class));
1980
1981 const ent_size = @sizeOf(class.ElfN().Sym);
1982 assert(elf.targetLoad(&dynsym_shdr.entsize) == ent_size);
1983
1984 // We're going to decrease the size of `.dynsym`, thereby removing its last index.
1985 const old_size = elf.targetLoad(&dynsym_shdr.size);
1986 const new_size = old_size - ent_size;
1987 const remove_dynsym_index: u32 = @intCast(@divExact(new_size, ent_size));
1988
1989 const free_dynsym_index = global_ptr.dynsym_index;
20071990 global_ptr.dynsym_index = 0;
1991
1992 if (free_dynsym_index != remove_dynsym_index) {
1993 // The demoted global wasn't the last entry, so move whatever entry we just
1994 // truncated out of dynsym into its place.
1995
1996 const src_dynsym_ptr = @field(elf.dynsymPtr(remove_dynsym_index), @tagName(class));
1997 const dest_dynsym_ptr = @field(elf.dynsymPtr(free_dynsym_index), @tagName(class));
1998
1999 const moved_name_dynstr: String(.dynstr) = @enumFromInt(elf.targetLoad(&src_dynsym_ptr.name));
2000 const moved_name = elf.stringExisting(.strtab, moved_name_dynstr.slice(elf));
2001 const moved_global_ptr = elf.globalByName(moved_name).?;
2002
2003 dest_dynsym_ptr.* = src_dynsym_ptr.*;
2004
2005 assert(moved_global_ptr.dynsym_index == remove_dynsym_index);
2006 moved_global_ptr.dynsym_index = free_dynsym_index;
2007
2008 // Since that symbol's dynsym index has changed, we'll have to update any
2009 // relocation entries targeting it.
2010 elf.changed_symtab_index.putAssumeCapacity(moved_name, {});
2011 }
2012
2013 // Now that we've given that symbol a new home, actually decrease the section size.
2014 elf.targetStore(&dynsym_shdr.size, new_size);
20082015 }
20092016 },
20102017 }
......@@ -2596,6 +2603,11 @@ fn string(elf: *Elf, comptime section: StringSection, key: []const u8) Error!Str
25962603 const st: *StringTable = &@field(elf, @tagName(section));
25972604 return @enumFromInt(try st.get(elf, section.shndx(elf), key));
25982605}
2606/// Like `string`, but asserts that the string is already in `section`.
2607fn stringExisting(elf: *Elf, comptime section: StringSection, key: []const u8) String(section) {
2608 const st: *StringTable = &@field(elf, @tagName(section));
2609 return @enumFromInt(st.getExisting(elf, section.shndx(elf), key));
2610}
25992611
26002612const StringTable = struct {
26012613 map: std.HashMapUnmanaged(u32, void, StringTable.Context, std.hash_map.default_max_load_percentage),
......@@ -2626,7 +2638,14 @@ const StringTable = struct {
26262638 }
26272639 };
26282640
2629 pub fn get(st: *StringTable, elf: *Elf, shndx: Section.Index, key: []const u8) Error!u32 {
2641 fn getExisting(st: *StringTable, elf: *Elf, shndx: Section.Index, key: []const u8) u32 {
2642 if (key.len == 0) return 0;
2643 const slice_const = shndx.get(elf).ni.sliceConst(&elf.mf);
2644 const adapter: StringTable.Adapter = .{ .slice = slice_const };
2645 return st.map.getKeyAdapted(key, adapter).?;
2646 }
2647
2648 fn get(st: *StringTable, elf: *Elf, shndx: Section.Index, key: []const u8) Error!u32 {
26302649 // If we are in `initHeaders` the strtab might not be initalized yet, so we need to special
26312650 // case the empty string.
26322651 if (key.len == 0) return 0;
......@@ -5201,7 +5220,7 @@ fn updateInitFiniArraySectionSize(
52015220 const end_vaddr: u64 = switch (elf.shdrPtr(shndx)) {
52025221 inline else => |shdr| shndx.vaddr(elf) + elf.targetLoad(&shdr.size),
52035222 };
5204 const end_sym_name = elf.string(.strtab, "__" ++ name ++ "_end") catch unreachable; // string definitely already exists
5223 const end_sym_name = elf.stringExisting(.strtab, "__" ++ name ++ "_end");
52055224 Symbol.Id.global(end_sym_name).flushMoved(elf, end_vaddr);
52065225}
52075226
......@@ -6491,26 +6510,72 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) link.Error!bool {
64916510 };
64926511 break :task;
64936512 }
6494 while (elf.changed_symtab_index.pop()) |kv| {
6495 // We only need to do work in relocatables, because in ELF modules (non-relocatables)
6496 // our `ElfN.Rela` entries use `.dynsym` indices rather than `.symtab` indices, and
6497 // `.dynsym` indices are (at the time of writing) always immutable.
6498 if (elf.ehdrField(.type) == .REL) {
6499 const sub_prog_node = elf.mf.update_prog_node.start(kv.key.slice(elf), 0);
6500 defer sub_prog_node.end();
6501 const sym = elf.globalByName(kv.key).?.symtab_index.ptr(elf);
6502 var ri = sym.first_target_reloc;
6503 while (ri != .none) {
6504 const reloc = ri.get(elf);
6505 reloc.relaSection(elf).relaUpdateSym(
6506 elf,
6507 reloc.rela_index.unwrap().?,
6508 @intFromEnum(reloc.target.index(elf)),
6509 );
6510 ri = reloc.next;
6511 }
6512 break :task;
6513 if (elf.changed_symtab_index.pop()) |kv| {
6514 const sub_prog_node = elf.mf.update_prog_node.start(kv.key.slice(elf), 0);
6515 defer sub_prog_node.end();
6516
6517 const global_name = kv.key;
6518 const global = elf.globalByName(global_name).?;
6519 const sym_id: Symbol.Id = .global(global_name);
6520 const sym = global.symtab_index.ptr(elf);
6521
6522 switch (elf.ehdrField(.type)) {
6523 .REL => {
6524 // Index in `.symtab` has changed. Relocatables are easy, we just need to update
6525 // all of the output relocations.
6526 const symtab_index = @intFromEnum(global.symtab_index);
6527 var ri = sym.first_target_reloc;
6528 while (ri != .none) {
6529 const reloc = ri.get(elf);
6530 assert(reloc.target == sym_id);
6531 // In relocatables, every symbol relocation has an output relocation.
6532 const rela_index = reloc.rela_index.unwrap().?;
6533 reloc.relaSection(elf).relaUpdateSym(elf, rela_index, symtab_index);
6534 ri = reloc.next;
6535 }
6536 },
6537 else => {
6538 // Index in `.dynsym` has changed. This case is slightly trickier because there
6539 // are a few things which may have emitted runtime relocations, including symbol
6540 // relocs...
6541 const dynsym_index = global.dynsym_index;
6542 var ri = sym.first_target_reloc;
6543 while (ri != .none) {
6544 const reloc = ri.get(elf);
6545 assert(reloc.target == sym_id);
6546 // There may or may not be a runtime relocation for this symbol reloc.
6547 if (reloc.rela_index.unwrap()) |rela_index| {
6548 reloc.relaSection(elf).relaUpdateSym(elf, rela_index, dynsym_index);
6549 }
6550 ri = reloc.next;
6551 }
6552
6553 // ...a copy relocation...
6554 if (elf.copied_globals.get(global_name)) |copied| {
6555 elf.shndx.rela_dyn.relaUpdateSym(elf, copied.rela_index, dynsym_index);
6556 }
6557
6558 // ...a PLT entry...
6559 if (elf.plt.getIndex(global_name)) |plt_index| {
6560 // PLT indices exactly match `.rela.plt` relocation indices.
6561 elf.shndx.rela_plt.relaUpdateSym(elf, @enumFromInt(plt_index), dynsym_index);
6562 }
6563
6564 // ...and any relevant GOT entries.
6565 if (elf.got.getIndex(.{ .symbol = sym_id })) |got_index| {
6566 elf.updateGotEntry(got_index);
6567 }
6568 if (elf.got.getIndex(.{ .tpoff = sym_id })) |got_index| {
6569 elf.updateGotEntry(got_index);
6570 }
6571 if (elf.got.getIndex(.{ .tlsgd0 = sym_id })) |got_index| {
6572 elf.updateGotEntry(got_index);
6573 elf.updateGotEntry(got_index + 1); // tlsgd1
6574 }
6575 },
65136576 }
6577
6578 break :task;
65146579 }
65156580 while (elf.mf.updates.pop()) |ni| {
65166581 const clean_moved = ni.cleanMoved(&elf.mf);