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 <n> code <n>", 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 <[email protected]>
This commit is contained in:
Yaya48andClaude Opus 5.5 committed 2026-10-13 21:21:58 +02:00
1 parent cdec826bb8
commit 612cab7b30
4 files changed
+84

No files matched your search

@@ -192,6 +192,14 @@ bool LoadBootstrapELF(std::span<const u8> 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<const u8> 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)
{
@@ -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.
@@ -4,6 +4,7 @@
#include <array>
#include <string>
#include <unordered_map>
#include <utility>
#include <gtest/gtest.h>
@@ -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<u8, 0x300> 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<u8>(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
@@ -1,6 +1,7 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
@@ -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<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 (memory.Read32(info.trace) != TRACE_MAGIC)
continue;
const u32 length = memory.Read32(info.trace + 4);