authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-17 14:22:30+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-19 18:30:34+01:00
logae7911ac25dfa12cd194c3801f996510824848d1
tree8e97fccbcb7444082c42df5fd91a3b5bae1f0d4c
parentcebeab2671e98de0d3c1d6677ebfa56c5e253077
signature Commit is signed but in an unrecognized format.

Elf2: fix `flushMoved` bubbling logic

When a loadable segment encounters a "moved" event, it should not propagate that event to child nodes, because they only care about virtual addresses changing and movement of segments in the file does not necessarily trigger a change in virtual address. The same goes for a moved event on a section without `SHF_ALLOC`: the "virtual addresses" of nodes within the section are actually just offsets into the section, so there's no need to ever propagate the event to them. This logic was correct in the past, but it unfortunately looks like I unintentionally regressed it in my early days of working on this linker. This commit fixes that regression, which results in redundant work being avoided and therefore speeds up linking.

1 files changed, 21 insertions(+), 11 deletions(-)

src/link/Elf2.zig+21-11
...@@ -5207,7 +5207,7 @@ fn initHeaders(...@@ -5207,7 +5207,7 @@ fn initHeaders(
5207 // Must be at least `addr_align` for `elf.ni.phdr` to be placed inside this node5207 // Must be at least `addr_align` for `elf.ni.phdr` to be placed inside this node
5208 .alignment = node_block_align.max(addr_align),5208 .alignment = node_block_align.max(addr_align),
5209 .moved = true,5209 .moved = true,
5210 .bubbles_moved = false,5210 .bubbles_moved = false, // see loadable segment handling in `flushMoved`
5211 }), .{ .segment = phndx.rodata });5211 }), .{ .segment = phndx.rodata });
5212 elf.phdrs.items[phndx.rodata] = .wrap(elf.ni.rodata);5212 elf.phdrs.items[phndx.rodata] = .wrap(elf.ni.rodata);
52135213
...@@ -5216,14 +5216,13 @@ fn initHeaders(...@@ -5216,14 +5216,13 @@ fn initHeaders(
5216 .alignment = addr_align, // keep in sync with `elf.ni.rodata` alignment above5216 .alignment = addr_align, // keep in sync with `elf.ni.rodata` alignment above
5217 .moved = true,5217 .moved = true,
5218 .resized = true,5218 .resized = true,
5219 .bubbles_moved = false,
5220 }), .{ .segment = phndx.phdr });5219 }), .{ .segment = phndx.phdr });
5221 elf.phdrs.items[phndx.phdr] = .wrap(elf.ni.phdr);5220 elf.phdrs.items[phndx.phdr] = .wrap(elf.ni.phdr);
52225221
5223 elf.ni.text = elf.addNodeAssumeCapacity(try elf.ni.elf.addFloatingChild(gpa, &elf.mf, .{5222 elf.ni.text = elf.addNodeAssumeCapacity(try elf.ni.elf.addFloatingChild(gpa, &elf.mf, .{
5224 .alignment = node_block_align,5223 .alignment = node_block_align,
5225 .moved = true,5224 .moved = true,
5226 .bubbles_moved = false,5225 .bubbles_moved = false, // see loadable segment handling in `flushMoved`
5227 }), .{ .segment = phndx.text });5226 }), .{ .segment = phndx.text });
5228 elf.phdrs.items[phndx.text] = .wrap(elf.ni.text);5227 elf.phdrs.items[phndx.text] = .wrap(elf.ni.text);
52295228
...@@ -5231,7 +5230,7 @@ fn initHeaders(...@@ -5231,7 +5230,7 @@ fn initHeaders(
5231 // Must be at least `addr_align` for `elf.ni.data_rel_ro` to be placed inside this node5230 // Must be at least `addr_align` for `elf.ni.data_rel_ro` to be placed inside this node
5232 .alignment = node_block_align.max(addr_align),5231 .alignment = node_block_align.max(addr_align),
5233 .moved = true,5232 .moved = true,
5234 .bubbles_moved = false,5233 .bubbles_moved = false, // see loadable segment handling in `flushMoved`
5235 }), .{ .segment = phndx.data });5234 }), .{ .segment = phndx.data });
5236 elf.phdrs.items[phndx.data] = .wrap(elf.ni.data);5235 elf.phdrs.items[phndx.data] = .wrap(elf.ni.data);
52375236
...@@ -5239,7 +5238,7 @@ fn initHeaders(...@@ -5239,7 +5238,7 @@ fn initHeaders(
5239 try elf.ni.elf.addFloatingChild(gpa, &elf.mf, .{5238 try elf.ni.elf.addFloatingChild(gpa, &elf.mf, .{
5240 .alignment = node_block_align,5239 .alignment = node_block_align,
5241 .moved = true,5240 .moved = true,
5242 .bubbles_moved = false,5241 .bubbles_moved = false, // see loadable segment handling in `flushMoved`
5243 }),5242 }),
5244 .{ .segment = phndx.plt },5243 .{ .segment = phndx.plt },
5245 ));5244 ));
...@@ -5249,7 +5248,6 @@ fn initHeaders(...@@ -5249,7 +5248,6 @@ fn initHeaders(
5249 // later (if `have_dynamic_section`). Keep in sync with `elf.ni.data` alignment above.5248 // later (if `have_dynamic_section`). Keep in sync with `elf.ni.data` alignment above.
5250 .alignment = node_block_align.max(addr_align),5249 .alignment = node_block_align.max(addr_align),
5251 .moved = true,5250 .moved = true,
5252 .bubbles_moved = false,
5253 }), .{ .segment = phndx.relro });5251 }), .{ .segment = phndx.relro });
5254 elf.phdrs.items[phndx.relro] = .wrap(elf.ni.data_rel_ro);5252 elf.phdrs.items[phndx.relro] = .wrap(elf.ni.data_rel_ro);
52555253
...@@ -5258,7 +5256,7 @@ fn initHeaders(...@@ -5258,7 +5256,7 @@ fn initHeaders(
5258 try elf.ni.rodata.addFloatingChild(gpa, &elf.mf, .{5256 try elf.ni.rodata.addFloatingChild(gpa, &elf.mf, .{
5259 .alignment = node_block_align,5257 .alignment = node_block_align,
5260 .moved = true,5258 .moved = true,
5261 .bubbles_moved = false,5259 .bubbles_moved = false, // see TLS segment handling in `flushMoved`
5262 }),5260 }),
5263 .{ .segment = phndx.tls },5261 .{ .segment = phndx.tls },
5264 ));5262 ));
...@@ -5676,7 +5674,6 @@ fn initHeaders(...@@ -5676,7 +5674,6 @@ fn initHeaders(
5676 .size = interp.len + 1,5674 .size = interp.len + 1,
5677 .moved = true,5675 .moved = true,
5678 .resized = true,5676 .resized = true,
5679 .bubbles_moved = false,
5680 }),5677 }),
5681 .{ .segment = phndx.interp },5678 .{ .segment = phndx.interp },
5682 );5679 );
...@@ -5698,7 +5695,6 @@ fn initHeaders(...@@ -5698,7 +5695,6 @@ fn initHeaders(
5698 try elf.ni.data_rel_ro.addFloatingChild(gpa, &elf.mf, .{5695 try elf.ni.data_rel_ro.addFloatingChild(gpa, &elf.mf, .{
5699 .alignment = addr_align,5696 .alignment = addr_align,
5700 .moved = true,5697 .moved = true,
5701 .bubbles_moved = false,
5702 }),5698 }),
5703 .{ .segment = phndx.dynamic },5699 .{ .segment = phndx.dynamic },
5704 );5700 );
...@@ -5952,7 +5948,6 @@ fn initHeaders(...@@ -5952,7 +5948,6 @@ fn initHeaders(
5952 .size = @sizeOf(Dwarf.EhFrameHdr),5948 .size = @sizeOf(Dwarf.EhFrameHdr),
5953 .alignment = .@"4",5949 .alignment = .@"4",
5954 .moved = true,5950 .moved = true,
5955 .bubbles_moved = false,
5956 }),5951 }),
5957 .{ .segment = phndx.gnu_eh_frame },5952 .{ .segment = phndx.gnu_eh_frame },
5958 );5953 );
...@@ -9119,7 +9114,7 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {...@@ -9119,7 +9114,7 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {
9119 .size = opts.node_align.forward(opts.size),9114 .size = opts.node_align.forward(opts.size),
9120 .alignment = opts.addralign.max(opts.node_align),9115 .alignment = opts.addralign.max(opts.node_align),
9121 .resized = opts.size > 0,9116 .resized = opts.size > 0,
9122 .bubbles_moved = opts.flags.ALLOC,9117 .bubbles_moved = opts.flags.ALLOC, // see handling in `flushMoved`
9123 }),9118 }),
9124 if (opts.manual_size) .{ .section_manual_size = shndx } else .{ .section = shndx },9119 if (opts.manual_size) .{ .section_manual_size = shndx } else .{ .section = shndx },
9125 )),9120 )),
...@@ -11309,6 +11304,9 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -11309,6 +11304,9 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
1130911304
11310 .NULL, .LOAD => {11305 .NULL, .LOAD => {
11311 try elf.allocateSegmentLoadAddress(phndx);11306 try elf.allocateSegmentLoadAddress(phndx);
11307 // Do not bubble moved---our child nodes only care if our virtual
11308 // address changes, which is handled by `updateSegmentLoadAddress`.
11309 return;
11312 },11310 },
1131311311
11314 .DYNAMIC,11312 .DYNAMIC,
...@@ -11336,6 +11334,12 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -11336,6 +11334,12 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
11336 elf,11334 elf,
11337 tbss_addr,11335 tbss_addr,
11338 );11336 );
11337
11338 // Do not bubble moved---our child nodes don't care about our virtual
11339 // address changing, because symbols in the TLS segment have values
11340 // defined relative to the base of the segment. (This is also why we
11341 // don't update tbss symbols here.)
11342 return;
11339 },11343 },
11340 }11344 }
11341 },11345 },
...@@ -11388,6 +11392,12 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void...@@ -11388,6 +11392,12 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void
11388 .first_symbol_reloc = elf.eh_frame_hdr_first_symbol_reloc,11392 .first_symbol_reloc = elf.eh_frame_hdr_first_symbol_reloc,
11389 });11393 });
11390 }11394 }
11395
11396 if (!flags.ALLOC) {
11397 // Do not bubble moved---our virtual address hasn't changed (because it's always 0
11398 // for non-ALLOC sections), so there's nothing for our children to worry about.
11399 return;
11400 }
11391 },11401 },
11392 .input_section => |isi| {11402 .input_section => |isi| {
11393 const old_section_addr = isi.ptr(elf).vaddr;11403 const old_section_addr = isi.ptr(elf).vaddr;