view shell live code: repoint already-bound symbol pointers, so a patch changes what draws

Merged
TE
2d ago
+59-0

Leo (2026-11-04): "Couldn't we architect stuff to make everything more amenable to hot swappage?"

The view shell's live code never changed what it drew. In the headless measurement (scripts/measure/live-code-shell.sh), every generation's badge was the same picture, grey with padding 8. Each patch loaded and replaced its functions (functions=4), but the drawing still ran the build's code. The measurement's "second patch redrew: yes" came from cmp on PNG bytes (2,036 vs 2,044 bytes for the same pixels).

Why. The executable is linked -interposable -no_fixup_chains, so its calls to its own Swift functions go through classic symbol pointers. One of them is __la_symbol_ptr for PreviewBadge.body (dyld_info -fixups: lazy-bind PreviewBadge.body). Generation 0 draws before any patch, so by then dyld has bound that pointer to the build's body, and dyld_dynamic_interpose does not rewrite a pointer that is already bound.

Fix. After the existing dyld_dynamic_interpose call, Interposer.rebind writes the patch's address into every __la_symbol_ptr and __got slot of the executable that names a replaced function, as fishhook does. __DATA_CONST is made writable for the write. The existing interpose call stays as it is.

Measured on a fleet Mac at this fix plus #35705. inject.sh was the only other change, to tar the PNGs out. The badge's rendered width, at 2× scale:

generationeditbefore this fixwith it
0none135 px, grey135 px, grey
1padding 8 → 24, red135 px, grey199 px, red (+64 = 2 × 16 pt × 2)
2padding 24 → 40135 px, grey263 px, red (+64)
3a new file's green dot (#35705)135 px, grey283 px, red, with the green dot (+20 = (6 + 4) pt × 2)
4a new @Staterefusedrefused

I checked the PNGs by eye as well. Edit to pixels: 1.45 s for the first patch and 1.14 s for the next.

Follow-up: the measurement should compare decoded pixels or widths, not bytes. It is a separate PR because the fleet runs only the reviewed bytes of a measurement script.

Part of manaflow-ai/cmuxterm-hq#7066.

🤖 Generated with Claude Code
Agent session: agent session


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the view shell's live code so a patch actually changes what draws: previously every generation of the headless measurement drew the same badge, because dyld_dynamic_interpose does not rewrite a symbol pointer dyld has already bound (the build's view had been drawn before any patch).

  • Adds Interposer.rebind, which after the interpose call writes the patch's address into every __la_symbol_ptr and __got slot that names a replaced function, as fishhook does.
  • Makes __DATA_CONST writable for the write via mprotect.
  • Measured result: the badge's padding and color changes from a patch now show in the rendered output, and a new file's drawing appears.

Migration

  • None. The executable's existing -interposable -no_fixup_chains linking is required for the rebind to be possible; that is already in place.