authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-27 14:01:24+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-28 16:50:43+00:00
logfca3f6f62e515669cb3aa22b8512e2cc4e1994f2
tree4f608a6f37f0109e48c8d22c0e7846e74d4561df
parent6ed9c0521055add99097fd43598d5a8e1e3665ff
signaturelock-open Commit is signed but in an unrecognized format.

MachO: don't split subsections on N_ALT_ENTRY symbols

MachO has a mechanism where symbols can introduce "subsections", which (as I understand it) allows a linker to garbage-collect parts of sections without pulling in the heavy machinery of `-fdata-sections` and `-ffunction-sections`. Essentially, symbols can be considered to partition a section, and these boundaries are not allowed to be crossed by memory accesses, so the linker can detect symbols which are unused and drop the corresponding input section regions. However, the symbol flag `N_ALT_ENTRY` indicates that a symbol should not participate in this "splitting", and is instead an "alternate entry point" to the previous subsection, which should continue through this symbol. The Mach-O linker was failing to ignore `N_ALT_ENTRY` symbols when creating subsections, which meant that for certain link inputs, it would create additional subsection splits, and then garbage collect the extra sections (due to the `N_ALT_ENTRY` symbol being unused). Naturally, this silent dropping of parts of input sections led to miscompilations.

1 files changed, 24 insertions(+), 5 deletions(-)

src/link/MachO/Object.zig+24-5
...@@ -328,7 +328,9 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {...@@ -328,7 +328,9 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {
328 if (isPtrLiteral(sect)) continue;328 if (isPtrLiteral(sect)) continue;
329329
330 const nlist_start = for (nlists, 0..) |nlist, i| {330 const nlist_start = for (nlists, 0..) |nlist, i| {
331 if (nlist.nlist.n_sect - 1 == n_sect) break i;331 // We must ignore `alt_entry` (N_ALT_ENTRY) symbols here, because that flag indicates
332 // that a symbol should *not* split subsections.
333 if (nlist.nlist.n_sect - 1 == n_sect and !nlist.nlist.n_desc.alt_entry) break i;
332 } else nlists.len;334 } else nlists.len;
333 const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {335 const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
334 if (nlist.nlist.n_sect - 1 != n_sect) break i;336 if (nlist.nlist.n_sect - 1 != n_sect) break i;
...@@ -359,9 +361,24 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {...@@ -359,9 +361,24 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {
359 const alias_start = idx;361 const alias_start = idx;
360 const nlist = nlists[alias_start];362 const nlist = nlists[alias_start];
361363
362 while (idx < nlist_end and364 // Skip past any symbols which shouldn't terminate this subsection.
363 nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)365 while (true) {
364 {}366 idx += 1;
367 if (idx == nlist_end) {
368 // This subsection contains the full remainder of the section.
369 break;
370 }
371 if (nlists[idx].nlist.n_value == nlist.nlist.n_value) {
372 // Multiple symbols at the same address---don't create zero-length subsections.
373 continue;
374 }
375 if (nlists[idx].nlist.n_desc.alt_entry) {
376 // N_ALT_ENTRY indicates that this symbol does not split subsections, and is
377 // instead an "alternate entry point" into an existing subsection.
378 continue;
379 }
380 break;
381 }
365382
366 const size = if (idx < nlist_end)383 const size = if (idx < nlist_end)
367 nlists[idx].nlist.n_value - nlist.nlist.n_value384 nlists[idx].nlist.n_value - nlist.nlist.n_value
...@@ -385,7 +402,9 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {...@@ -385,7 +402,9 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {
385 });402 });
386403
387 for (alias_start..idx) |i| {404 for (alias_start..idx) |i| {
388 self.symtab.items(.size)[nlists[i].idx] = size;405 if (!nlists[i].nlist.n_desc.alt_entry) {
406 self.symtab.items(.size)[nlists[i].idx] = size;
407 }
389 }408 }
390 }409 }
391410