Fix the netboot launcher: relocate the copier, reserve the staging region
The launcher committed in b438960 would have hung the console, and reviewing
it against Dennis's suggestion to test with Swiss's boot.dol is what surfaced
it -- Swiss is known-good, so a hang could only have been the launcher, but I
would not have been able to tell that from "the transfer is broken".
Two distinct defects, both from the same oversight -- a DOL's sections load
from 0x80003100 upward, which is exactly where this program is running:
1. The copier was an ordinary C function in .text, so it overwrote its OWN
instructions partway through the copy.
2. The image was staged in a memalign'd buffer, which the destination sections
can overwrite while it is still being read from.
Fixes:
- netboot_stub.S: the copy/zero/cache-flush/jump sequence in position
independent assembly, copied into reserved memory and executed THERE. It
has no globals, no literal pool, no TOC references and no calls, because
any of those would read from the address it was LINKED at rather than the
one it is RUNNING at. It also does the dcbf/icbi pass the C version was
doing through DCFlushRange -- without it the CPU executes stale icache
lines at the entry point.
- netboot_reserve(): carves the top of MEM1 and hands it to
SYS_SetArena1Hi() so neither libogc nor malloc allocates there. Called
FIRST in main(), before the framebuffer.
- netboot_launch(): verifies every text/data section AND bss against the
reserved region and refuses (-3) if any would land on it, instead of
corrupting the staged image or the copier mid-flight. Also hands the new
image a stack inside the reserved tail.
The app now tells you on screen to send Swiss's boot.dol first, and the README
explains why: the launcher and the transfer can each be broken independently
and a hang looks identical, so the first send should be an image whose success
is unmistakable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+29
-3
@@ -107,6 +107,32 @@ Failure behaviour, so a bad update never costs a power cycle:
|
||||
- `netboot_launch()` refuses an image whose entry point or first text section
|
||||
is not in MEM1, rather than jumping into a mis-parsed header.
|
||||
|
||||
The one unavoidable risk is the jump itself: once sections are copied and
|
||||
interrupts are off, a bad image hangs the console and needs a power cycle. The
|
||||
header check above is what stands between a truncated transfer and that.
|
||||
### Send Swiss's `boot.dol` first
|
||||
|
||||
The launcher and the transfer are separate things that can each be broken, and
|
||||
a hang looks the same either way. Send a **known-good** image the first time —
|
||||
Swiss's `boot.dol` is ideal, because it is unmistakable when it boots. If
|
||||
Swiss comes up, both halves work and you can then trust an untested build.
|
||||
|
||||
### Why the copier lives in assembly at the top of MEM1
|
||||
|
||||
A DOL's sections load from 0x80003100 upward — exactly where this program's own
|
||||
`.text` sits. A copier compiled as an ordinary C function would **overwrite its
|
||||
own instructions partway through the copy** and hang. So:
|
||||
|
||||
- `netboot_reserve()` carves the top of MEM1 and hands it to
|
||||
`SYS_SetArena1Hi()`, so neither libogc nor `malloc` will touch it. The image
|
||||
is staged there, never in heap memory.
|
||||
- `netboot_stub.S` is copied into that region and executed *there*, clear of
|
||||
everything the image writes. It must stay strictly position independent: no
|
||||
globals, no literal pool, no TOC references, no calls — otherwise it reads
|
||||
from the address it was *linked* at instead of the one it is *running* at.
|
||||
- `netboot_launch()` checks every section (and BSS) against the reserved
|
||||
region and **refuses** if any would land on it, rather than corrupting the
|
||||
image or the copier mid-flight.
|
||||
|
||||
The remaining risk is the jump itself: once sections are copied and interrupts
|
||||
are off, a bad image hangs the console and needs a power cycle. The header and
|
||||
overlap checks are what stand between a truncated transfer and that — they
|
||||
cannot validate a *plausible* but broken image, which is why the first send
|
||||
should be Swiss.
|
||||
|
||||
Reference in New Issue
Block a user