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:
@@ -188,42 +188,106 @@ typedef struct {
|
||||
u32 pad[7];
|
||||
} dolhdr_t;
|
||||
|
||||
/* Reserved region at the top of MEM1:
|
||||
*
|
||||
* [ staged image .............. ][ stub ][ descriptor ][ new stack ]
|
||||
* ^ s_stage ^ s_stub
|
||||
*
|
||||
* Nothing here may overlap any DOL section, and netboot_launch() checks that
|
||||
* before it jumps rather than trusting it. */
|
||||
#define NB_STAGE_MAX (6u << 20) /* generous: Swiss is ~2-3 MB */
|
||||
#define NB_TAIL (64u << 10) /* stub + descriptor + stack */
|
||||
|
||||
extern void nb_stub(void *desc);
|
||||
extern void nb_stub_end(void);
|
||||
|
||||
static u8 *s_stage;
|
||||
static u8 *s_tail;
|
||||
|
||||
void *netboot_reserve(u32 *out_max)
|
||||
{
|
||||
u32 hi = (u32)SYS_GetArena1Hi();
|
||||
u32 base = (hi - (NB_STAGE_MAX + NB_TAIL)) & ~31u;
|
||||
|
||||
if (base <= (u32)SYS_GetArena1Lo()) return NULL;
|
||||
s_stage = (u8 *)base;
|
||||
s_tail = (u8 *)(base + NB_STAGE_MAX);
|
||||
/* Tell libogc this is not its memory any more. */
|
||||
SYS_SetArena1Hi(s_stage);
|
||||
if (out_max) *out_max = NB_STAGE_MAX;
|
||||
return s_stage;
|
||||
}
|
||||
|
||||
static int overlaps(u32 a, u32 alen, u32 b, u32 blen)
|
||||
{
|
||||
if (!alen || !blen) return 0;
|
||||
return a < b + blen && b < a + alen;
|
||||
}
|
||||
|
||||
int netboot_launch(const void *dol)
|
||||
{
|
||||
const dolhdr_t *h = (const dolhdr_t *)dol;
|
||||
const u8 *base = (const u8 *)dol;
|
||||
void (*entry)(void);
|
||||
int i;
|
||||
u32 reserved = (u32)s_stage;
|
||||
u32 reserved_len = NB_STAGE_MAX + NB_TAIL;
|
||||
u32 stub_len = (u32)((const u8 *)nb_stub_end - (const u8 *)nb_stub);
|
||||
u32 *desc;
|
||||
void (*stub)(void *);
|
||||
int i, nsec = 0;
|
||||
|
||||
/* Sanity: the entry point and at least one text section must live in
|
||||
* MEM1. Jumping to a mis-parsed header hangs the console hard enough
|
||||
* to need a power cycle, so refuse rather than guess. */
|
||||
if (!s_stage) return -1;
|
||||
|
||||
/* Refuse a mis-parsed or truncated header rather than jumping into it:
|
||||
* a bad jump hangs the console hard enough to need a power cycle. */
|
||||
if ((h->entry >> 24) != 0x80 || h->text_len[0] == 0 ||
|
||||
(h->text_addr[0] >> 24) != 0x80)
|
||||
return -1;
|
||||
|
||||
/* No interrupts from here on: we are about to overwrite the memory this
|
||||
* program is running from, and with interrupts off the scheduler cannot
|
||||
* preempt us into code that no longer exists. */
|
||||
IRQ_Disable();
|
||||
return -2;
|
||||
|
||||
/* Build the copy table in the reserved tail, where the image cannot
|
||||
* reach it. While doing so, verify no section lands on the reserved
|
||||
* region -- that would clobber the staged image, the stub we are about
|
||||
* to run from, or this very table. */
|
||||
desc = (u32 *)(s_tail + 0x1000);
|
||||
for (i = 0; i < 7; i++) {
|
||||
if (!h->text_len[i]) continue;
|
||||
memmove((void *)h->text_addr[i], base + h->text_off[i], h->text_len[i]);
|
||||
DCFlushRange((void *)h->text_addr[i], h->text_len[i]);
|
||||
ICInvalidateRange((void *)h->text_addr[i], h->text_len[i]);
|
||||
if (overlaps(h->text_addr[i], h->text_len[i], reserved, reserved_len))
|
||||
return -3;
|
||||
desc[5 + nsec * 3 + 0] = (u32)(base + h->text_off[i]);
|
||||
desc[5 + nsec * 3 + 1] = h->text_addr[i];
|
||||
desc[5 + nsec * 3 + 2] = h->text_len[i];
|
||||
nsec++;
|
||||
}
|
||||
for (i = 0; i < 11; i++) {
|
||||
if (!h->data_len[i]) continue;
|
||||
memmove((void *)h->data_addr[i], base + h->data_off[i], h->data_len[i]);
|
||||
DCFlushRange((void *)h->data_addr[i], h->data_len[i]);
|
||||
}
|
||||
if (h->bss_len) {
|
||||
memset((void *)h->bss_addr, 0, h->bss_len);
|
||||
DCFlushRange((void *)h->bss_addr, h->bss_len);
|
||||
if (overlaps(h->data_addr[i], h->data_len[i], reserved, reserved_len))
|
||||
return -3;
|
||||
desc[5 + nsec * 3 + 0] = (u32)(base + h->data_off[i]);
|
||||
desc[5 + nsec * 3 + 1] = h->data_addr[i];
|
||||
desc[5 + nsec * 3 + 2] = h->data_len[i];
|
||||
nsec++;
|
||||
}
|
||||
if (h->bss_len &&
|
||||
overlaps(h->bss_addr, h->bss_len, reserved, reserved_len))
|
||||
return -3;
|
||||
|
||||
entry = (void (*)(void))h->entry;
|
||||
entry();
|
||||
desc[0] = h->entry;
|
||||
/* Hand the new image a stack inside the reserved tail, 64-byte aligned
|
||||
* and clear of the descriptor. */
|
||||
desc[1] = ((reserved + reserved_len) - 64) & ~63u;
|
||||
desc[2] = (u32)nsec;
|
||||
desc[3] = h->bss_addr;
|
||||
desc[4] = h->bss_len;
|
||||
|
||||
/* Relocate the copier out of harm's way and make it executable there. */
|
||||
stub = (void (*)(void *))s_tail;
|
||||
memcpy(s_tail, (const void *)nb_stub, stub_len);
|
||||
DCFlushRange(s_tail, NB_TAIL);
|
||||
ICInvalidateRange(s_tail, NB_TAIL);
|
||||
|
||||
/* No interrupts from here on: we are about to overwrite the memory this
|
||||
* program is running from, and the scheduler must not preempt us into
|
||||
* code that no longer exists. */
|
||||
IRQ_Disable();
|
||||
stub(desc);
|
||||
return 0; /* not reached */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user