StarletBootstrapProbe: wait for StarOS's whole panic path
--expect-panic passed as soon as __panic_state was nonzero at the end of a 4096-cycle slice. StarOS's handler stores the guard first, then the code, then publishes the SBR1 record, so the run could stop with the path half done. The probe now keeps running until the path is finished and still is two slices later, and fails if the run budget ends first: - __panic_state 1 and __panic_code 1; - with --sbr1-record: STARLET_STATE 5 (Panic), FATAL_REASON 0 and EXI_OWNER 1 (handed back to the PPC); - with --sbr1-absent: every word of the record area still zero. The failure names the first unmet condition. The check is StarOSPanicCheck.h, pure, with the SBR1 constants moved there, and StarletProbePanicCheckTest covers each condition; each of 7 mutants of the check fails a test. Runs that do not expect a panic are unchanged. With StarOS's panic test image (eb930ee), interpreter and JIT: the scenarios pass, two slices later than before. Three images derived from it pass the previous probe and now fail: EXI kept by the Starlet (--sbr1-record), __panic_code never stored, the record written although absent (--sbr1-absent). (StarOS MMU-001 review of F1.) Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a62c65615a
commit
6ede30f99e
4 files changed
+254
-39
No files matched your search
@@ -17,9 +17,12 @@
|
||||
#include "Core/IOS/Starlet/BootstrapELF.h"
|
||||
#include "Core/IOS/Starlet/StarletMemory.h"
|
||||
#include "Core/System.h"
|
||||
#include "StarOSPanicCheck.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace StarletBootstrapProbe;
|
||||
|
||||
enum class ExpectedOutcome
|
||||
{
|
||||
Idle,
|
||||
@@ -32,21 +35,8 @@ 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;
|
||||
// Slices run once a StarOS panic path looks finished, to see that it stays so.
|
||||
constexpr int PANIC_SETTLE_SLICES = 2;
|
||||
|
||||
// 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.
|
||||
@@ -57,7 +47,8 @@ bool FailOnAlert(const char* caption, const char* text, bool, Common::MsgType)
|
||||
std::_Exit(ALERT_EXIT_STATUS);
|
||||
}
|
||||
|
||||
bool Run(const std::string& path, bool jit, bool mmu_faults, ExpectedOutcome expected)
|
||||
bool Run(const std::string& path, bool jit, bool mmu_faults, SBR1Mode sbr1,
|
||||
ExpectedOutcome expected)
|
||||
{
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file || file.tellg() <= 0 || file.tellg() > 16 * 1024 * 1024)
|
||||
@@ -95,6 +86,26 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, ExpectedOutcome exp
|
||||
core.SetMMUFaultsEnabled(mmu_faults);
|
||||
constexpr u32 TRACE_MAGIC = 0x52455631;
|
||||
constexpr u32 TRACE_CAPACITY = 1024;
|
||||
auto& mem1 = system.GetMemory();
|
||||
const auto read_mem1 = [&mem1](u32 address) { return mem1.Read_U32(address); };
|
||||
const auto panic_unmet = [&] {
|
||||
return UnmetPanicCondition(memory.Read32(info.panic_state), memory.Read32(info.panic_code),
|
||||
sbr1, read_mem1);
|
||||
};
|
||||
// StarOS's panic path writes no trace line. Print the trace published so far, for the
|
||||
// scenario's ordered checks, and the panic words.
|
||||
const auto report_panic = [&] {
|
||||
const u32 length = memory.Read32(info.trace) == TRACE_MAGIC ?
|
||||
std::min(memory.Read32(info.trace + 4), TRACE_CAPACITY) :
|
||||
0;
|
||||
for (u32 index = 0; index < length; ++index)
|
||||
std::cout << static_cast<char>(memory.Read8(info.trace + 8 + index));
|
||||
std::cout << "PC=0x" << std::hex << core.GetRegister(15) << std::dec
|
||||
<< " instructions=" << core.GetExecutedInstructions() << '\n';
|
||||
std::cerr << "Starlet panic state " << memory.Read32(info.panic_state) << " code "
|
||||
<< memory.Read32(info.panic_code) << '\n';
|
||||
};
|
||||
int panic_settled = 0;
|
||||
for (int slice = 0; slice < 512; ++slice)
|
||||
{
|
||||
core.SetIRQLine(system.GetWiiIPC().IsStarletIRQAsserted());
|
||||
@@ -109,21 +120,25 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, ExpectedOutcome exp
|
||||
return expected == ExpectedOutcome::UndefinedFault && fault_code == 1 &&
|
||||
memory.Read32(info.fault_lr) != 0 && memory.Read32(info.fault_spsr) != 0;
|
||||
}
|
||||
// StarOS's panic path writes no trace line: it sets its guard word, then its code, and halts.
|
||||
// The trace published so far is printed for the scenario's ordered checks.
|
||||
// StarOS's panic path sets its guard word first, then its code, then publishes the SBR1
|
||||
// record when there is one, and halts. An expected panic passes once all of that holds
|
||||
// (UnmetPanicCondition) and still holds PANIC_SETTLE_SLICES slices later.
|
||||
if (info.panic_state != 0 && memory.Read32(info.panic_state) != 0)
|
||||
{
|
||||
const u32 panic_state = memory.Read32(info.panic_state);
|
||||
const u32 panic_code = memory.Read32(info.panic_code);
|
||||
const u32 length = memory.Read32(info.trace) == TRACE_MAGIC ?
|
||||
std::min(memory.Read32(info.trace + 4), TRACE_CAPACITY) :
|
||||
0;
|
||||
for (u32 index = 0; index < length; ++index)
|
||||
std::cout << static_cast<char>(memory.Read8(info.trace + 8 + index));
|
||||
std::cout << "PC=0x" << std::hex << core.GetRegister(15) << std::dec
|
||||
<< " instructions=" << core.GetExecutedInstructions() << '\n';
|
||||
std::cerr << "Starlet panic state " << panic_state << " code " << panic_code << '\n';
|
||||
return expected == ExpectedOutcome::Panic && panic_state == 1;
|
||||
if (expected != ExpectedOutcome::Panic)
|
||||
{
|
||||
report_panic();
|
||||
return false;
|
||||
}
|
||||
if (!panic_unmet().empty())
|
||||
{
|
||||
panic_settled = 0;
|
||||
continue;
|
||||
}
|
||||
if (++panic_settled <= PANIC_SETTLE_SLICES)
|
||||
continue;
|
||||
report_panic();
|
||||
return true;
|
||||
}
|
||||
if (memory.Read32(info.trace) != TRACE_MAGIC)
|
||||
continue;
|
||||
@@ -174,6 +189,14 @@ bool Run(const std::string& path, bool jit, bool mmu_faults, ExpectedOutcome exp
|
||||
expected == ExpectedOutcome::Idle && trace.find("REV:BOOT:IDLE\n") != std::string::npos;
|
||||
return startup_ok && outcome_ok;
|
||||
}
|
||||
if (info.panic_state != 0 && memory.Read32(info.panic_state) != 0)
|
||||
{
|
||||
report_panic();
|
||||
const std::string unmet = panic_unmet();
|
||||
std::cerr << "StarOS panic path unfinished after 2097152 ARM cycles: "
|
||||
<< (unmet.empty() ? "it ended inside the settle slices" : unmet) << '\n';
|
||||
return false;
|
||||
}
|
||||
std::cerr << "No terminal bootstrap marker after 2097152 ARM cycles, PC=0x" << std::hex
|
||||
<< core.GetRegister(15) << std::dec << '\n';
|
||||
return false;
|
||||
@@ -246,21 +269,22 @@ int main(int argc, char** argv)
|
||||
// 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_EXI_STARLET, SBR1_BASE + SBR1_EXI_OWNER);
|
||||
memory.Write_U32(SBR1_RELAY_JUMPING, SBR1_BASE + SBR1_RELAY_STATUS);
|
||||
memory.Write_U32(SBR1_MAGIC, SBR1_BASE);
|
||||
}
|
||||
const bool passed = Run(argv[1], jit, mmu_faults, expected);
|
||||
const bool passed = Run(argv[1], jit, mmu_faults, sbr1, expected);
|
||||
if (sbr1 != SBR1Mode::None)
|
||||
{
|
||||
// The record's summary after the run; with --sbr1-absent every word must still be zero.
|
||||
// The record's summary after the run; with --sbr1-absent every word must still be zero (an
|
||||
// expected panic fails otherwise).
|
||||
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);
|
||||
<< " state " << memory.Read_U32(SBR1_BASE + SBR1_STARLET_STATE) << " reason "
|
||||
<< memory.Read_U32(SBR1_BASE + SBR1_FATAL_REASON) << " owner "
|
||||
<< memory.Read_U32(SBR1_BASE + SBR1_EXI_OWNER) << " fault "
|
||||
<< memory.Read_U32(SBR1_BASE + SBR1_FAULT_CODE) << " ring "
|
||||
<< memory.Read_U32(SBR1_BASE + SBR1_RING_LENGTH) << " dropped "
|
||||
<< memory.Read_U32(SBR1_BASE + SBR1_RING_DROPPED);
|
||||
u32 written = 0;
|
||||
for (u32 offset = 0; offset < SBR1_SIZE; offset += 4)
|
||||
written += memory.Read_U32(SBR1_BASE + offset) != 0;
|
||||
|
||||
Reference in new issue
Block a user