From 612cab7b308f80cad1e9369826299d26f5de4e09 Mon Sep 17 00:00:00 2001 From: Yaya48 Date: Tue, 13 Oct 2026 21:21:58 +0200 Subject: [PATCH] StarletBootstrapProbe: report StarOS's panic words StarOS's panic path (ADR-0014 R6) writes no trace line: it sets the guard word __panic_state, stores __panic_code and halts. LoadBootstrapELF now returns both symbols when an image has them (optional, but both or neither, as words of the data segment), and the probe stops a run whose guard is set: it prints the trace published so far and "Starlet panic state code ", and passes --expect-panic when the state is 1. Images without these symbols (StarletOS) load and run as before; a REV:PANIC trace line still satisfies --expect-panic. Co-Authored-By: Claude Opus 5.5 --- Source/Core/Core/IOS/Starlet/BootstrapELF.cpp | 14 ++++++ Source/Core/Core/IOS/Starlet/BootstrapELF.h | 4 ++ .../Core/IOS/Starlet/BootstrapELFTest.cpp | 49 +++++++++++++++++++ .../StarletBootstrapProbe.cpp | 17 +++++++ 4 files changed, 84 insertions(+) diff --git a/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp b/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp index 34275212aa..fdbb27da7e 100644 --- a/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp +++ b/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp @@ -192,6 +192,14 @@ bool LoadBootstrapELF(std::span image, ARMBus& bus, BootstrapELFInfo* parsed.fault_spsr = address; found_fault_spsr = true; } + else if (*name == "__panic_state") + { + parsed.panic_state = address; + } + else if (*name == "__panic_code") + { + parsed.panic_code = address; + } } } const auto data_segment = @@ -208,6 +216,12 @@ bool LoadBootstrapELF(std::span image, ARMBus& bus, BootstrapELFInfo* { return Fail(error, "bootstrap ELF is missing the trace or fault symbols"); } + // The panic words are optional, but both or neither, as words of the data segment. + if ((parsed.panic_state != 0) != (parsed.panic_code != 0) || + (parsed.panic_state != 0 && (!in_data(parsed.panic_state, 4) || !in_data(parsed.panic_code, 4)))) + { + return Fail(error, "bootstrap ELF has an invalid panic symbol"); + } for (const Segment& segment : segments) { diff --git a/Source/Core/Core/IOS/Starlet/BootstrapELF.h b/Source/Core/Core/IOS/Starlet/BootstrapELF.h index 015ce1f598..208cb06a61 100644 --- a/Source/Core/Core/IOS/Starlet/BootstrapELF.h +++ b/Source/Core/Core/IOS/Starlet/BootstrapELF.h @@ -19,6 +19,10 @@ struct BootstrapELFInfo u32 fault_code = 0; u32 fault_lr = 0; u32 fault_spsr = 0; + // Optional (StarOS ADR-0014 R6): the panic path's guard and code words, 0 when the image has + // none. A nonzero guard means the panic handler ran. + u32 panic_state = 0; + u32 panic_code = 0; }; // Load the Phase 01 ELF into a bus already configured with the high SRAM mapping. diff --git a/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp b/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp index cbdf391d9e..919f2a1116 100644 --- a/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp +++ b/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -232,5 +233,53 @@ TEST(ARMCoreTest, Armv5DoublewordTransfersAndAlignmentAbort) EXPECT_EQ(core.GetRegister(14), TEXT_BASE + 8); EXPECT_EQ(core.GetCP15State().fault_address, DATA_BASE + 4); } + +// StarOS ADR-0014 R6: the optional panic words. The string table moves past the section headers +// to make room for two more symbols. +std::array MakePanicImage(u32 state, u32 code) +{ + auto image = MakeImage(); + constexpr char names[] = + "\0REV_TRACE\0__fault_code\0__fault_lr\0__fault_spsr\0__panic_state\0__panic_code"; + for (size_t index = 0; index < sizeof(names); ++index) + image[0x278 + index] = static_cast(names[index]); + Put32(image, 0x228 + 16, 0x278); + Put32(image, 0x228 + 20, sizeof(names)); + Put32(image, 0x250 + 20, 7 * 16); + const auto symbol = [&image](size_t index, u32 name, u32 address) { + const size_t offset = 0x120 + index * 16; + Put32(image, offset, name); + Put32(image, offset + 4, address); + Put32(image, offset + 8, 4); + }; + // An omitted word keeps its slot, with the empty name. + symbol(5, state != 0 ? 48 : 0, state); + symbol(6, code != 0 ? 62 : 0, code); + return image; +} + +TEST(BootstrapELFTest, PanicWordsAreOptionalButBothInData) +{ + RecordingBus bus; + BootstrapELFInfo info; + std::string error; + ASSERT_TRUE(LoadBootstrapELF(MakeImage(), bus, &info, &error)) << error; + EXPECT_EQ(info.panic_state, 0u); + EXPECT_EQ(info.panic_code, 0u); + + ASSERT_TRUE(LoadBootstrapELF(MakePanicImage(DATA_BASE + 0x41c, DATA_BASE + 4), bus, &info, &error)) + << error; + EXPECT_EQ(info.panic_state, DATA_BASE + 0x41c); + EXPECT_EQ(info.panic_code, DATA_BASE + 4); + + for (const auto [state, code] : {std::pair{DATA_BASE + 0x41c, 0u}, std::pair{0u, DATA_BASE + 4}, + std::pair{DATA_BASE + 0x41c, DATA_BASE + 0x420}}) + { + RecordingBus rejected; + EXPECT_FALSE(LoadBootstrapELF(MakePanicImage(state, code), rejected, &info, &error)); + EXPECT_EQ(error, "bootstrap ELF has an invalid panic symbol"); + EXPECT_TRUE(rejected.bytes.empty()); + } +} } // namespace } // namespace IOS::LLE diff --git a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp index 5263fb307a..a6b306d70b 100644 --- a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp +++ b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp @@ -1,6 +1,7 @@ // Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -92,6 +93,22 @@ 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. + 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(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 (memory.Read32(info.trace) != TRACE_MAGIC) continue; const u32 length = memory.Read32(info.trace + 4);