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:
2026-10-09 11:13:32 +02:00
parent b438960eb5
commit 4fd337fb24
5 changed files with 245 additions and 43 deletions
+25 -18
View File
@@ -21,6 +21,8 @@
#include "netboot.h"
static void *xfb = NULL;
static void *s_stage = NULL; /* netboot staging region (top of MEM1) */
static u32 s_stage_max = 0;
static GXRModeObj *rmode = NULL;
#define C_RESET "\x1b[37;0m"
@@ -125,25 +127,23 @@ static void nb_status(const char *msg)
static void do_netboot(char *status, size_t nstatus)
{
void *buf;
int len;
int len, rc;
printf("\n" C_HEAD " Self-update over the BBA" C_RESET "\n\n");
printf(" On the host: sudo ./scripts/send-dol.py <iface> <new.dol>\n");
printf(" B on the next screen is NOT available -- this blocks until the\n"
" transfer finishes or times out (30 s idle, 5 s mid-transfer).\n\n");
printf(" On the host: sudo ./scripts/send-dol.py <iface> <file.dol>\n\n");
printf(C_DIM " Send a KNOWN-GOOD image the first time -- Swiss's boot.dol\n"
" is ideal: it is unmistakable when it boots, so it tells you the\n"
" transfer AND the launcher both work before you trust either\n"
" with an untested build.\n" C_RESET "\n");
printf(" Blocks until done or it times out (30 s idle, 5 s mid-xfer).\n\n");
/* 8 MB ceiling: far above any .dol, and it keeps a corrupt length field
* from trying to allocate the whole heap. */
buf = memalign(32, 8u << 20);
if (!buf) {
snprintf(status, nstatus, "net-update: out of memory");
if (!s_stage) {
snprintf(status, nstatus, "net-update: staging region unavailable");
return;
}
len = netboot_receive(buf, 8u << 20, nb_status);
len = netboot_receive(s_stage, s_stage_max, nb_status);
if (len <= 0) {
free(buf);
snprintf(status, nstatus, "net-update failed (%d)", len);
return;
}
@@ -154,12 +154,13 @@ static void do_netboot(char *status, size_t nstatus)
VIDEO_WaitVSync();
VIDEO_WaitVSync();
if (netboot_launch(buf) < 0) {
free(buf);
snprintf(status, nstatus, "not a valid DOL - not launching");
return;
}
/* netboot_launch() does not return on success. */
rc = netboot_launch(s_stage);
/* Only reached on refusal. -2 = header implausible (truncated or not a
* DOL); -3 = a section would land on the reserved staging region, which
* would corrupt the image or the copier mid-flight. */
snprintf(status, nstatus,
rc == -3 ? "refused: image overlaps staging region"
: "refused: not a plausible DOL");
}
int main(void)
@@ -168,6 +169,12 @@ int main(void)
int sel = 0, ssel = 0, cur = 0;
char status[80] = "";
/* Reserve the netboot staging region FIRST, before libogc allocates the
* framebuffer or anything else out of the arena. A staged image must
* not live in ordinary heap memory: DOL sections load from 0x80003100
* upward and would overwrite it partway through the copy. */
s_stage = netboot_reserve(&s_stage_max);
VIDEO_Init();
PAD_Init();
rmode = VIDEO_GetPreferredMode(NULL);