jevsnes.git / tools / hotpatch / CLAUDE.md
1@README.md
2
3- **Linux/x86_64 only, on purpose.** No `target_lexicon`, no per-OS branches
4  in `src/main.rs` - this ports the *logic* of dioxus-cli's `patch.rs`, not
5  its full cross-platform surface. Adding another OS means adding the real
6  branch (Mach-O headers, Windows stub asm, …), not guessing at one.
7- **TLS symbols are handled by copying init bytes, not by refusing them.**
8  `create_undefined_symbol_stub`'s `SymbolKind::Tls` arm copies the base
9  binary's own `.tdata` bytes (`CachedSymbol::address`/`size` are the OFFSET
10  and length into that section, not a virtual address - the one symbol kind
11  here `aslr_offset` does not apply to) into a fresh TLS symbol in the patch,
12  porting `patch.rs`'s ELF branch (`patch.rs:1163-1226` in the dioxus
13  checkout). **This was not a theoretical risk**: proof against the real
14  window (`apps/native`) hit it immediately, on a plain recompile with NO
15  source changes to `packages/panels` at all - moving `logic`/`ui`'s bodies
16  into free functions shifted codegen-unit boundaries enough that a
17  thread-local internal to `std` or `tokio` came out newly-undefined in the
18  fresh link. `apps/hotdemo` never exercises this (no tokio, a two-crate
19  dependency graph) - do not treat it passing as evidence this branch is
20  unneeded for anything bigger. What is still genuinely unhandled: a symbol
21  that is TLS in the NEW code but has no counterpart in the base binary's
22  cache at all (nothing to copy init bytes from) - that still exits loudly,
23  correctly, since there is nothing to port it from.
24- `patch.sh`'s crate name comes from the target label's own suffix
25  (`//apps/hotdemo:hotdemo` -> `hotdemo`), and that name has to match: the
26  log file (`run/<crate>.log`), the `--out-dir XIPL/extras/<crate>` buck2
27  produces, and the patch-json path the running binary watches
28  (`run/<crate>.patch.json`) all derive from it. A target whose crate name
29  differs from its label's tail (an alias, an unusual `crate` attr) needs
30  `patch.sh` taught about the difference, not a rename to force the
31  convention.
32- Re-run `research/subsecond-patch-build.md` §7's measurements (the
33  `XIPL/extras` path shape, the `-Clink-dead-code` symbol-count diff) if this
34  is ever pointed at a different buck2 prelude version - both are read from
35  the prelude's `.bzl`/measured against a real build, not documented
36  upstream API.
37- **Never remove `-Csave-temps=true`/`-Clink-dead-code` from
38  `apps/native:native`'s own `BUCK` rule, even though `main.rs` has no
39  patch points.** `patch.sh` reads that object off disk (never rebuilding
40  it) as the "main" sentinel `subsecond::apply_patch` looks up by name in
41  every patch's own link - see README's "the executable is never rebuilt"
42  section. Removing the flags "cleans up" a real dependency, silently:
43  `hotpatch` fails loudly ("the patch has no 'main' symbol") the next time
44  anyone patches `apps/native`, not when the flags are removed.
45- **A tip's first-party dependencies are walked TRANSITIVELY
46  (`all_first_party_deps_of`), not one level.** `apps/native:native` ->
47  `apps/native:app` -> `packages/panels` is two hops; a one-level walk finds
48  `app` and stops, so a `packages/panels` edit builds correct fresh objects
49  that never reach `hotpatch` - the patch "succeeds" and silently keeps
50  running the OLD panels code via the undefined-symbol stub. No error
51  anywhere; this is the same family of silent failure as the TLS trap above,
52  found the same way (proving against the real window, 2026-09-20). Adding
53  another hop to the dependency graph needs no patch.sh change - the walk
54  is already general - but REMOVING the transitive walk to "simplify" it
55  reintroduces this exact bug.
56- **A dependency is checked with `has_save_temps` BEFORE it is built, not
57  after.** Building it first to find out costs real time for nothing:
58  `packages/mcp` depends on `packages/panels`, so a panels edit invalidates
59  mcp's own (proc-macro-heavy) compile too, for a result that was always
60  going to be thrown away (~10s measured 2026-09-20, before this check
61  moved earlier in `patch.sh`'s dependency loop).
62- **`--base` is `/proc/$PID/exe`, never a path `buck2 build` just produced.**
63  Asking buck2 to build `//apps/native:native` for `--base` is what forces
64  the relink this whole design exists to avoid the moment `app` has
65  diverged from what shipped in the last real build of the executable -
66  `/proc/$PID/exe` is the OS's own answer to "what is this pid actually
67  running", free and correct by construction, whether or not buck2's
68  on-disk copy at that path has since changed underneath it.