StarletBootstrapProbe: --sbr1-record and --sbr1-absent

StarOS's SBR1 boot record (MEM1 0x00200000) is created by its Broadway
loader before the image runs; the probe never had one, so the paths
that publish into it ran only on the console. --sbr1-record writes the
record as the loader and the ARM relay leave it (header, relay at its
last step, EXI channel 1 given to the Starlet, magic last); after the run
both options print the record's magic, state, reason, EXI owner, fault
code, ring length and dropped count, and how many of its 1024 words are
nonzero (0 is expected with --sbr1-absent). Without either option the
probe behaves as before.

With the StarOS M1 images (interpreter and JIT): idle publishes state 3
and hands EXI back (owner 1, ring 258 bytes, no Gecko emulated);
fatal-data state 4 reason 3; undefined-entry state 6, fault 1, ring
empty, as on the console (gates HW2-HW4).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Yaya48andClaude Opus 5.5 committed 2026-10-13 22:40:28 +02:00
1 parent 2bae49bdad
commit 15da28ad7b
1 file changed
+55 -1
@@ -32,6 +32,22 @@ enum class ExpectedOutcome
// Exit status of a run stopped by a Dolphin alert (1: failed expectation, 2: usage).
constexpr int ALERT_EXIT_STATUS = 3;
// StarOS's SBR1 boot record in MEM1 (StarOS crates/boot-contract, report.rs).
enum class SBR1Mode
{
None,
// --sbr1-record: create the record as the loader does, report it after the run.
Record,
// --sbr1-absent: no record; report the area after the run.
Absent,
};
constexpr u32 SBR1_BASE = 0x00200000;
constexpr u32 SBR1_MAGIC = 0x53425231;
constexpr u32 SBR1_VERSION = 1;
constexpr u32 SBR1_SIZE = 0x1000;
constexpr u32 SBR1_EXI_STARLET = 2;
constexpr u32 SBR1_RELAY_JUMPING = 4;
// Dolphin's default handler shows an alert in a modal MessageBox on Windows, which blocks the
// probe until someone closes it. Report the alert on stderr and fail the run at once instead.
bool FailOnAlert(const char* caption, const char* text, bool, Common::MsgType)
@@ -167,14 +183,16 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, ExpectedOutcome exp
int main(int argc, char** argv)
{
Common::RegisterMsgAlertHandler(FailOnAlert);
if (argc < 2 || argc > 5)
if (argc < 2 || argc > 6)
{
std::cerr << "Usage: StarletBootstrapProbe <ELF> [--jit] [--mmu-faults] "
"[--sbr1-record|--sbr1-absent] "
"[--expect-panic|--expect-fatal-data|--expect-undefined-fault|--expect-phase2]\n";
return 2;
}
bool jit = false;
bool mmu_faults = false;
SBR1Mode sbr1 = SBR1Mode::None;
ExpectedOutcome expected = ExpectedOutcome::Idle;
bool outcome_selected = false;
for (int index = 2; index < argc; ++index)
@@ -184,6 +202,15 @@ int main(int argc, char** argv)
jit = true;
else if (option == "--mmu-faults")
mmu_faults = true;
else if (option == "--sbr1-record" || option == "--sbr1-absent")
{
if (sbr1 != SBR1Mode::None)
{
std::cerr << "Select only one SBR1 mode\n";
return 2;
}
sbr1 = option == "--sbr1-record" ? SBR1Mode::Record : SBR1Mode::Absent;
}
else if (option == "--expect-panic" || option == "--expect-fatal-data" ||
option == "--expect-undefined-fault" || option == "--expect-phase2")
{
@@ -211,7 +238,34 @@ int main(int argc, char** argv)
auto& system = Core::System::GetInstance();
system.SetIsWii(true);
system.GetMemory().Init();
auto& memory = system.GetMemory();
if (sbr1 == SBR1Mode::Record)
{
// As the Broadway loader and the ARM relay leave StarOS's SBR1 boot record before the image
// runs (StarOS docs/contracts/wii-dev-boot.md): header, the relay at its last step, EXI
// channel 1 given to the Starlet, the magic written last.
memory.Write_U32(SBR1_VERSION, SBR1_BASE + 0x04);
memory.Write_U32(SBR1_SIZE, SBR1_BASE + 0x08);
memory.Write_U32(SBR1_EXI_STARLET, SBR1_BASE + 0x0c);
memory.Write_U32(SBR1_RELAY_JUMPING, SBR1_BASE + 0x10);
memory.Write_U32(SBR1_MAGIC, SBR1_BASE);
}
const bool passed = Run(argv[1], jit, mmu_faults, expected);
if (sbr1 != SBR1Mode::None)
{
// The record's summary after the run; with --sbr1-absent every word must still be zero.
std::cerr << std::hex << "SBR1 magic 0x" << memory.Read_U32(SBR1_BASE) << std::dec
<< " state " << memory.Read_U32(SBR1_BASE + 0x18) << " reason "
<< memory.Read_U32(SBR1_BASE + 0x1c) << " owner "
<< memory.Read_U32(SBR1_BASE + 0x0c) << " fault "
<< memory.Read_U32(SBR1_BASE + 0x20) << " ring "
<< memory.Read_U32(SBR1_BASE + 0x2c) << " dropped "
<< memory.Read_U32(SBR1_BASE + 0x30);
u32 written = 0;
for (u32 offset = 0; offset < SBR1_SIZE; offset += 4)
written += memory.Read_U32(SBR1_BASE + offset) != 0;
std::cerr << " nonzero_words " << written << '\n';
}
system.GetMemory().Shutdown();
return passed ? 0 : 1;
}