authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-16 17:39:50+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-16 18:44:29+01:00
log716b778d5564a671148cc524d845f016364d1cec
tree025b5ec28b8dca7d52bf18a95e0da0494a5b1fb8
parenta32fb5b5276ba6dca4bedcf3b225499c70dd53d4
signature Commit is signed but in an unrecognized format.

Elf2: refactor updates of .[t]bss addresses


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

src/link/Elf2.zig+116-110
...@@ -151,6 +151,11 @@ bss_inputs: std.ArrayList(Node.InputIndex),...@@ -151,6 +151,11 @@ bss_inputs: std.ArrayList(Node.InputIndex),
151/// symbols when the address of `.tbss` changes.151/// symbols when the address of `.tbss` changes.
152tbss_inputs: std.ArrayList(Node.InputIndex),152tbss_inputs: std.ArrayList(Node.InputIndex),
153153
154/// Indicates that the address of the `.bss` section needs to be updated.
155///
156/// Equivalent to a `PROGBITS` section's `MappedFile.Node` being marked as "moved".
157bss_moved: bool,
158
154shstrtab: StringTable,159shstrtab: StringTable,
155strtab: StringTable,160strtab: StringTable,
156dynstr: StringTable,161dynstr: StringTable,
...@@ -4815,6 +4820,7 @@ fn create(...@@ -4815,6 +4820,7 @@ fn create(
4815 .tbss_globals = .empty,4820 .tbss_globals = .empty,
4816 .bss_inputs = .empty,4821 .bss_inputs = .empty,
4817 .tbss_inputs = .empty,4822 .tbss_inputs = .empty,
4823 .bss_moved = false,
4818 .shstrtab = .{ .map = .empty },4824 .shstrtab = .{ .map = .empty },
4819 .strtab = .{ .map = .empty },4825 .strtab = .{ .map = .empty },
4820 .dynstr = .{ .map = .empty },4826 .dynstr = .{ .map = .empty },
...@@ -6426,20 +6432,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {...@@ -6426,20 +6432,12 @@ fn getNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
6426 };6432 };
6427}6433}
64286434
6429fn tbssBaseOffset(elf: *Elf) u64 {6435fn tbssOffset(elf: *Elf) u64 {
6430 const tbss_addr = elf.shndx.tbss.vaddr(elf);6436 // The `.tbss` section is placed exactly at the end of the TLS segment.
64316437 const tls_phndx = elf.getNode(elf.ni.tls.unwrap().?).segment;
6432 // Hasn't been laid out yet.6438 return switch (elf.phdrSlice()) {
6433 if (tbss_addr == 0 or elf.ehdrType() == .REL) return 0;6439 inline else => |phdr| elf.targetLoad(&phdr[tls_phndx].filesz),
6434
6435 const tls_ni = elf.ni.tls.unwrap().?;
6436 const tls_phndx = elf.getNode(tls_ni).segment;
6437
6438 const tls_vaddr = switch (elf.phdrSlice()) {
6439 inline else => |phdr| elf.targetLoad(&phdr[tls_phndx].vaddr),
6440 };6440 };
6441
6442 return tbss_addr - tls_vaddr;
6443}6441}
64446442
6445fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {6443fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
...@@ -7921,7 +7919,7 @@ fn loadObject(...@@ -7921,7 +7919,7 @@ fn loadObject(
7921 if (elf.ni.tls != .none and7919 if (elf.ni.tls != .none and
7922 nobits.shndx == elf.shndx.tbss)7920 nobits.shndx == elf.shndx.tbss)
7923 {7921 {
7924 break :base elf.tbssBaseOffset();7922 break :base elf.tbssOffset();
7925 }7923 }
7926 assert(nobits.shndx == elf.shndx.bss);7924 assert(nobits.shndx == elf.shndx.bss);
7927 break :base nobits.shndx.vaddr(elf);7925 break :base nobits.shndx.vaddr(elf);
...@@ -10871,12 +10869,46 @@ pub fn idle(elf: *Elf) link.Error!bool {...@@ -10871,12 +10869,46 @@ pub fn idle(elf: *Elf) link.Error!bool {
10871 if (clean_moved or clean_resized or clean_next_moved) try elf.flushPadding(ni);10869 if (clean_moved or clean_resized or clean_next_moved) try elf.flushPadding(ni);
10872 break :task;10870 break :task;
10873 }10871 }
10872 if (elf.bss_moved) {
10873 elf.bss_moved = false;
10874
10875 const data_phndx = elf.getNode(elf.ni.data).segment;
10876 const data_offset: u64, const data_vaddr: u64, const data_filesz: u64 = switch (elf.phdrSlice()) {
10877 inline else => |phdr| .{
10878 elf.targetLoad(&phdr[data_phndx].offset),
10879 elf.targetLoad(&phdr[data_phndx].vaddr),
10880 elf.targetLoad(&phdr[data_phndx].filesz),
10881 },
10882 };
10883
10884 const old_bss_addr = elf.shndx.bss.vaddr(elf);
10885 const new_bss_addr = data_vaddr + data_filesz;
10886
10887 const bss_section_sym: Symbol.Id = .local(elf.shndx.bss.get(elf).lsi);
10888 bss_section_sym.flushMoved(elf, new_bss_addr);
10889 switch (elf.shdrPtr(elf.shndx.bss)) {
10890 inline else => |shdr| {
10891 elf.targetStore(&shdr.offset, @intCast(data_offset + data_filesz));
10892 elf.targetStore(&shdr.addr, @intCast(new_bss_addr));
10893 },
10894 }
10895 elf.flushMovedNobitsSymbols(
10896 elf.shndx.bss,
10897 old_bss_addr,
10898 new_bss_addr,
10899 elf.bss_inputs.items,
10900 elf.bss_globals.keys(),
10901 );
10902
10903 break :task;
10904 }
10874 }10905 }
10875 if (elf.inputs.items.len - elf.input_pending_index > 0) return true;10906 if (elf.inputs.items.len - elf.input_pending_index > 0) return true;
10876 if (elf.input_sections.items.len - elf.input_section_pending_index > 0) return true;10907 if (elf.input_sections.items.len - elf.input_section_pending_index > 0) return true;
10877 if (elf.one_shot_fixups.items.len > 0) return true;10908 if (elf.one_shot_fixups.items.len > 0) return true;
10878 if (elf.changed_symtab_index.count() > 0) return true;10909 if (elf.changed_symtab_index.count() > 0) return true;
10879 if (elf.mf.updates.items.len > 0) return true;10910 if (elf.mf.updates.items.len > 0) return true;
10911 if (elf.bss_moved) return true;
10880 return false;10912 return false;
10881}10913}
1088210914
...@@ -11231,8 +11263,23 @@ fn flushElfOffset(elf: *Elf, ni: MappedFile.Node.Index) void {...@@ -11231,8 +11263,23 @@ fn flushElfOffset(elf: *Elf, ni: MappedFile.Node.Index) void {
11231 inline else => |phdr, class| {11263 inline else => |phdr, class| {
11232 const ph = &phdr[phndx];11264 const ph = &phdr[phndx];
11233 elf.targetStore(&ph.offset, @intCast(elf_offset));11265 elf.targetStore(&ph.offset, @intCast(elf_offset));
11234 if (elf.targetLoad(&ph.type) == .PHDR) {11266 switch (elf.targetLoad(&ph.type)) {
11235 @field(elf.ehdrPtr(), @tagName(class)).phoff = ph.offset;11267 .PHDR => {
11268 const ehdr = @field(elf.ehdrPtr(), @tagName(class));
11269 elf.targetStore(&ehdr.phoff, @intCast(elf_offset));
11270 },
11271 .TLS => {
11272 // The `.tbss` section's offset should be at the end of the TLS segment.
11273 const tls_filesz = elf.targetLoad(&ph.filesz);
11274 const tbss_shdr = @field(elf.shdrPtr(elf.shndx.tbss), @tagName(class));
11275 elf.targetStore(&tbss_shdr.offset, @intCast(elf_offset + tls_filesz));
11276 },
11277 else => if (ni == elf.ni.data) {
11278 // The `.bss` section's offset should be at the end of the data segment.
11279 const data_filesz = elf.targetLoad(&ph.filesz);
11280 const bss_shdr = @field(elf.shdrPtr(elf.shndx.bss), @tagName(class));
11281 elf.targetStore(&bss_shdr.offset, @intCast(elf_offset + data_filesz));
11282 },
11236 }11283 }
11237 },11284 },
11238 }11285 }
...@@ -11283,23 +11330,16 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -11283,23 +11330,16 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
11283 },11330 },
1128411331
11285 .TLS => {11332 .TLS => {
11286 const tbss_base = elf.tbssBaseOffset();
11287 const new_vaddr = elf.computeNodeVAddr(ni);11333 const new_vaddr = elf.computeNodeVAddr(ni);
11288 const tls_offset = elf.targetLoad(&ph.offset);
11289 const tls_filesz = elf.targetLoad(&ph.filesz);
11290
11291 elf.targetStore(&ph.vaddr, @intCast(new_vaddr));11334 elf.targetStore(&ph.vaddr, @intCast(new_vaddr));
11292 elf.targetStore(&ph.paddr, @intCast(new_vaddr));11335 elf.targetStore(&ph.paddr, @intCast(new_vaddr));
1129311336
11294 const tbss_shndx = elf.shndx.tbss;11337 // Update the address of the `.tbss` section at the end of this segment:
11295 const tbss_addr = new_vaddr + tbss_base;11338 const tbss_addr = new_vaddr + elf.tbssOffset();
11296 switch (elf.shdrPtr(tbss_shndx)) {11339 switch (elf.shdrPtr(elf.shndx.tbss)) {
11297 inline else => |shdr| {11340 inline else => |shdr| elf.targetStore(&shdr.addr, @intCast(tbss_addr)),
11298 elf.targetStore(&shdr.addr, @intCast(tbss_addr));
11299 elf.targetStore(&shdr.offset, @intCast(tls_offset + tls_filesz));
11300 },
11301 }11341 }
11302 Symbol.Id.local(tbss_shndx.get(elf).lsi).flushMoved(11342 Symbol.Id.local(elf.shndx.tbss.get(elf).lsi).flushMoved(
11303 elf,11343 elf,
11304 tbss_addr,11344 tbss_addr,
11305 );11345 );
...@@ -11634,13 +11674,15 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -11634,13 +11674,15 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
11634 try ni.childrenMoved(elf.base.comp.gpa, &elf.mf);11674 try ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
11635}11675}
1163611676
11637/// Update the position of a `NOBITS` section.11677/// Update the value of all symbols in a `NOBITS` section. All symbol values are updated to
11638/// BSS needs an absolute symbol base, and TBSS needs TLS-segment-relative base.11678/// `old_value - old_symbol_base + new_symbol_base`.
11639fn updateNobitsAddress(11679///
11680/// For `.bss`, the symbol base is the section's virtual address.
11681///
11682/// For `.tbss`, the symbol base is the offset into the TLS segment.
11683fn flushMovedNobitsSymbols(
11640 elf: *Elf,11684 elf: *Elf,
11641 shndx: Section.Index,11685 shndx: Section.Index,
11642 new_section_addr: u64,
11643 new_file_offset: u64,
11644 old_symbol_base: u64,11686 old_symbol_base: u64,
11645 new_symbol_base: u64,11687 new_symbol_base: u64,
11646 inputs: []const Node.InputIndex,11688 inputs: []const Node.InputIndex,
...@@ -11648,13 +11690,6 @@ fn updateNobitsAddress(...@@ -11648,13 +11690,6 @@ fn updateNobitsAddress(
11648) void {11690) void {
11649 assert(shndx != .UNDEF);11691 assert(shndx != .UNDEF);
1165011692
11651 switch (elf.shdrPtr(shndx)) {
11652 inline else => |shdr| {
11653 elf.targetStore(&shdr.addr, @intCast(new_section_addr));
11654 elf.targetStore(&shdr.offset, @intCast(new_file_offset));
11655 },
11656 }
11657
11658 // Update local symbols.11693 // Update local symbols.
11659 for (inputs) |ii| {11694 for (inputs) |ii| {
11660 var lsi, const end_lsi = ii.localSymbolRange(elf);11695 var lsi, const end_lsi = ii.localSymbolRange(elf);
...@@ -11662,14 +11697,14 @@ fn updateNobitsAddress(...@@ -11662,14 +11697,14 @@ fn updateNobitsAddress(
11662 while (lsi != end_lsi) : (lsi = @fromBackingInt(11697 while (lsi != end_lsi) : (lsi = @fromBackingInt(
11663 @backingInt(lsi) + 1,11698 @backingInt(lsi) + 1,
11664 )) {11699 )) {
11665 const symbol_shndx: Section.Index, const visibility = switch (elf.symPtr(lsi.index())) {11700 const sym_shndx: Section.Index, const visibility = switch (elf.symPtr(lsi.index())) {
11666 inline else => |sym| .{11701 inline else => |sym| .{
11667 .fromSection(elf.targetLoad(&sym.shndx)),11702 .fromSection(elf.targetLoad(&sym.shndx)),
11668 elf.targetLoad(&sym.other).visibility,11703 elf.targetLoad(&sym.other).visibility,
11669 },11704 },
11670 };11705 };
1167111706
11672 if (symbol_shndx != shndx) continue;11707 if (sym_shndx != shndx) continue;
1167311708
11674 switch (visibility) {11709 switch (visibility) {
11675 // These are globals demoted to STB_LOCAL and are updated below.11710 // These are globals demoted to STB_LOCAL and are updated below.
...@@ -11700,40 +11735,26 @@ fn updateNobitsAddress(...@@ -11700,40 +11735,26 @@ fn updateNobitsAddress(
11700 symbol.value(elf) - old_symbol_base + new_symbol_base,11735 symbol.value(elf) - old_symbol_base + new_symbol_base,
11701 );11736 );
11702 }11737 }
11703
11704 Symbol.Id.local(shndx.get(elf).lsi).flushMoved(
11705 elf,
11706 new_section_addr,
11707 );
11708}11738}
1170911739
11710fn loadSegmentChildrenMoved(11740/// Updates `vaddr` and `paddr` of the segment header, and marks everything in the segment as moved.
11741fn updateSegmentLoadAddress(
11711 elf: *Elf,11742 elf: *Elf,
11712 segment_ni: MappedFile.Node.Index,11743 segment_ni: MappedFile.Node.Index,
11713 old_vaddr: u64,
11714 new_vaddr: u64,11744 new_vaddr: u64,
11715 offset: u64,
11716 filesz: u64,
11717) std.mem.Allocator.Error!void {11745) std.mem.Allocator.Error!void {
11718 if (segment_ni == elf.ni.data) {11746 const phndx = elf.getNode(segment_ni).segment;
11719 const bss_shndx = elf.shndx.bss;
11720 const old_bss_addr = bss_shndx.vaddr(elf);
11721 const new_bss_addr = new_vaddr + filesz;
11722
11723 elf.updateNobitsAddress(
11724 bss_shndx,
11725 new_bss_addr,
11726 offset + filesz,
11727 old_bss_addr,
11728 new_bss_addr,
11729 elf.bss_inputs.items,
11730 elf.bss_globals.keys(),
11731 );
11732 }
1173311747
11734 if (old_vaddr != new_vaddr) {11748 switch (elf.phdrSlice()) {
11735 try segment_ni.childrenMoved(elf.base.comp.gpa, &elf.mf);11749 inline else => |phdr| {
11750 assert(new_vaddr != elf.targetLoad(&phdr[phndx].vaddr));
11751 elf.targetStore(&phdr[phndx].vaddr, @intCast(new_vaddr));
11752 elf.targetStore(&phdr[phndx].paddr, @intCast(new_vaddr));
11753 },
11736 }11754 }
11755
11756 try segment_ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
11757 if (segment_ni == elf.ni.data) elf.bss_moved = true;
11737}11758}
1173811759
11739/// Given the index of a `PT_LOAD`/`PT_NULL` segment, assumes that the phdr's `offset` and `filesz`11760/// Given the index of a `PT_LOAD`/`PT_NULL` segment, assumes that the phdr's `offset` and `filesz`
...@@ -11852,19 +11873,7 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro...@@ -11852,19 +11873,7 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro
11852 const next_offset = elf.targetLoad(&next_ph.offset);11873 const next_offset = elf.targetLoad(&next_ph.offset);
11853 const next_new_vaddr = next_align.forward(free_vaddr) + next_offset % next_align.toByteUnits();11874 const next_new_vaddr = next_align.forward(free_vaddr) + next_offset % next_align.toByteUnits();
1185411875
11855 // This logic for updating the data segment's vaddr is identical to how we11876 try elf.updateSegmentLoadAddress(next_ni, next_new_vaddr);
11856 // will update the vaddr of `phndx` when we break from the loop.
11857 elf.targetStore(&next_ph.vaddr, @intCast(next_new_vaddr));
11858 elf.targetStore(&next_ph.paddr, @intCast(next_new_vaddr));
11859
11860 const next_filesz = elf.targetLoad(&next_ph.filesz);
11861 try elf.loadSegmentChildrenMoved(
11862 next_ni,
11863 next_vaddr,
11864 next_new_vaddr,
11865 next_offset,
11866 next_filesz,
11867 );
1186811877
11869 break;11878 break;
11870 },11879 },
...@@ -11887,17 +11896,8 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro...@@ -11887,17 +11896,8 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro
11887 }11896 }
1188811897
11889 if (vaddr != orig_vaddr) {11898 if (vaddr != orig_vaddr) {
11890 elf.targetStore(&phdr[phndx].vaddr, @intCast(vaddr));11899 try elf.updateSegmentLoadAddress(segment_ni, vaddr);
11891 elf.targetStore(&phdr[phndx].paddr, @intCast(vaddr));
11892 }11900 }
11893
11894 try elf.loadSegmentChildrenMoved(
11895 segment_ni,
11896 orig_vaddr,
11897 vaddr,
11898 offset,
11899 filesz,
11900 );
11901 },11901 },
11902 }11902 }
11903}11903}
...@@ -11926,40 +11926,46 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo...@@ -11926,40 +11926,46 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo
11926 .ehdr => unreachable,11926 .ehdr => unreachable,
11927 .shdr => {},11927 .shdr => {},
11928 .segment => |phndx| switch (elf.phdrSlice()) {11928 .segment => |phndx| switch (elf.phdrSlice()) {
11929 inline else => |phdr| {11929 inline else => |phdr, class| {
11930 assert(elf.phdrs.items[phndx].unwrap().? == ni);11930 assert(elf.phdrs.items[phndx].unwrap().? == ni);
11931 const ph = &phdr[phndx];11931 const ph = &phdr[phndx];
11932 elf.targetStore(&ph.filesz, @intCast(size));
11933 switch (elf.targetLoad(&ph.type)) {11932 switch (elf.targetLoad(&ph.type)) {
11934 else => unreachable,11933 else => unreachable,
11935 .NULL, .LOAD => {11934 .NULL, .LOAD => {
11935 elf.targetStore(&ph.filesz, @intCast(size));
11936 try elf.allocateSegmentLoadAddress(phndx);11936 try elf.allocateSegmentLoadAddress(phndx);
11937 if (ni == elf.ni.data) elf.bss_moved = true;
11937 },11938 },
11938 .DYNAMIC, .INTERP, .PHDR, .GNU_EH_FRAME, .GNU_RELRO => {11939 .DYNAMIC, .INTERP, .PHDR, .GNU_EH_FRAME, .GNU_RELRO => {
11940 elf.targetStore(&ph.filesz, @intCast(size));
11939 elf.targetStore(&ph.memsz, @intCast(size));11941 elf.targetStore(&ph.memsz, @intCast(size));
11940 },11942 },
11941 .TLS => {11943 .TLS => {
11942 const tls_filesz: u64 = size;11944 // We must fetch this value before modifying our `filesz`.
11945 const old_tbss_offset = elf.tbssOffset();
1194311946
11944 const old_tbss_base = elf.tbssBaseOffset();11947 elf.targetStore(&ph.filesz, @intCast(size));
11945 const new_tbss_base = tls_filesz;
11946 const tbss_size = elf.shndx.tbss.size(elf);
11947 const tls_vaddr = elf.targetLoad(&ph.vaddr);
11948 const tls_offset = elf.targetLoad(&ph.offset);
1194911948
11950 const tls_memsz = std.mem.alignForward(11949 // Our memsz must include the `.tbss` section.
11951 u64,11950 const tls_align: Alignment = .fromByteUnits(elf.targetLoad(&ph.@"align"));
11952 tls_filesz + tbss_size,11951 elf.targetStore(&ph.memsz, @intCast(
11953 elf.targetLoad(&ph.@"align"),11952 tls_align.forward(size + elf.shndx.tbss.size(elf)),
11954 );11953 ));
11955 elf.targetStore(&ph.memsz, @intCast(tls_memsz));
1195611954
11957 elf.updateNobitsAddress(11955 // `.tbss` section goes at the end of this segment. Update its offset:
11956 const tls_offset = elf.targetLoad(&ph.offset);
11957 const tbss_shdr = @field(elf.shdrPtr(elf.shndx.tbss), @tagName(class));
11958 elf.targetStore(&tbss_shdr.offset, @intCast(tls_offset + size));
11959 // and its address:
11960 const tls_vaddr = elf.targetLoad(&ph.vaddr);
11961 const tbss_section_sym: Symbol.Id = .local(elf.shndx.tbss.get(elf).lsi);
11962 tbss_section_sym.flushMoved(elf, tls_vaddr + size);
11963 elf.targetStore(&tbss_shdr.addr, @intCast(tls_vaddr + size));
11964 // and update the values of symbols in that section:
11965 elf.flushMovedNobitsSymbols(
11958 elf.shndx.tbss,11966 elf.shndx.tbss,
11959 tls_vaddr + new_tbss_base,11967 old_tbss_offset,
11960 tls_offset + new_tbss_base,11968 size,
11961 old_tbss_base,
11962 new_tbss_base,
11963 elf.tbss_inputs.items,11969 elf.tbss_inputs.items,
11964 elf.tbss_globals.keys(),11970 elf.tbss_globals.keys(),
11965 );11971 );