IOS LLE: shadow comparison for REV_WATCH (StarletOS phase 11 H1)
Reads and IPC-reply audits compare each watched range with what the Starlet and the devices wrote, revealing other writers; register-list transfers in a watched virtual range log their translation. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
76a133f3a4
commit
ba5b6de0d0
7 files changed
+192
-16
No files matched your search
@@ -12,6 +12,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/PerformanceTrace.h"
|
#include "Common/PerformanceTrace.h"
|
||||||
#if defined(_M_X86_64)
|
#if defined(_M_X86_64)
|
||||||
// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a
|
// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a
|
||||||
@@ -36,9 +37,24 @@ constexpr u32 ModeValue(ARMCore::Mode mode)
|
|||||||
ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory)
|
ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory)
|
||||||
: m_bus(bus), m_profile_jit_memory(profile_jit_memory)
|
: m_bus(bus), m_profile_jit_memory(profile_jit_memory)
|
||||||
{
|
{
|
||||||
|
m_watch_ranges = m_bus.GetWatchRanges();
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ARMCore::TraceWatchedBlockTransfer(u32 address, u32 size, bool load) const
|
||||||
|
{
|
||||||
|
// StarletOS phase 11 (H1): the virtual range of a register-list transfer and the physical
|
||||||
|
// address the interpreter translates its first word to. A difference shows a transfer that
|
||||||
|
// does not read or write where the program addressed it.
|
||||||
|
const bool overlaps = std::ranges::any_of(m_watch_ranges, [&](const auto& range) {
|
||||||
|
return u64{address} < u64{range.first} + range.second && u64{address} + size > range.first;
|
||||||
|
});
|
||||||
|
if (!overlaps)
|
||||||
|
return;
|
||||||
|
NOTICE_LOG_FMT(IOS, "REVWATCH {} {:08x}->{:08x}/{} PC {:08x}", load ? "LDM" : "STM", address,
|
||||||
|
TranslateVirtualAddress(address), size, m_instruction_address);
|
||||||
|
}
|
||||||
|
|
||||||
ARMCore::~ARMCore() = default;
|
ARMCore::~ARMCore() = default;
|
||||||
|
|
||||||
void ARMCore::SetJitEnabled(bool enabled)
|
void ARMCore::SetJitEnabled(bool enabled)
|
||||||
@@ -2252,6 +2268,8 @@ void ARMCore::ExecuteBlockDataTransfer(u32 instruction)
|
|||||||
address = preindex ? base + 4 : base;
|
address = preindex ? base + 4 : base;
|
||||||
else
|
else
|
||||||
address = preindex ? base - count * 4 : base - (count - 1) * 4;
|
address = preindex ? base - count * 4 : base - (count - 1) * 4;
|
||||||
|
if (!m_watch_ranges.empty())
|
||||||
|
TraceWatchedBlockTransfer(address, count * 4, load);
|
||||||
|
|
||||||
u32 loaded_pc = 0;
|
u32 loaded_pc = 0;
|
||||||
bool pc_loaded = false;
|
bool pc_loaded = false;
|
||||||
|
|||||||
@@ -311,6 +311,8 @@ private:
|
|||||||
void ExecuteSingleDataTransfer(u32 instruction);
|
void ExecuteSingleDataTransfer(u32 instruction);
|
||||||
void ExecuteHalfwordDataTransfer(u32 instruction);
|
void ExecuteHalfwordDataTransfer(u32 instruction);
|
||||||
void ExecuteBlockDataTransfer(u32 instruction);
|
void ExecuteBlockDataTransfer(u32 instruction);
|
||||||
|
// Phase 11 H1: a register-list transfer overlapping a watched range, with its translation.
|
||||||
|
void TraceWatchedBlockTransfer(u32 address, u32 size, bool load) const;
|
||||||
void ExecuteMultiply(u32 instruction);
|
void ExecuteMultiply(u32 instruction);
|
||||||
void ExecuteMultiplyLong(u32 instruction);
|
void ExecuteMultiplyLong(u32 instruction);
|
||||||
void ExecuteBranch(u32 instruction);
|
void ExecuteBranch(u32 instruction);
|
||||||
@@ -369,6 +371,8 @@ private:
|
|||||||
u32 m_memory_poll_address = 0;
|
u32 m_memory_poll_address = 0;
|
||||||
bool m_pc_written = false;
|
bool m_pc_written = false;
|
||||||
u32 m_instruction_address = 0;
|
u32 m_instruction_address = 0;
|
||||||
|
// Phase 11 H1: the bus's watched ranges (REV_WATCH), empty when off.
|
||||||
|
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
||||||
u32 m_last_undefined_instruction = 0;
|
u32 m_last_undefined_instruction = 0;
|
||||||
u64 m_executed_instructions = 0;
|
u64 m_executed_instructions = 0;
|
||||||
u64 m_memory_poll_entry_count = 0;
|
u64 m_memory_poll_entry_count = 0;
|
||||||
|
|||||||
@@ -1476,6 +1476,15 @@ bool ARMJitX64::EmitARMBlockTransfer(u32 instruction, u32 address, bool* termina
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<FixupBranch> slow_paths;
|
std::vector<FixupBranch> slow_paths;
|
||||||
|
// StarletOS phase 11 (H1): a transfer whose virtual range overlaps a watched range runs through
|
||||||
|
// the architectural helper, so the interpreter logs its translation.
|
||||||
|
for (const auto& [start, size] : m_watch_ranges)
|
||||||
|
{
|
||||||
|
MOV(32, R(EDX), R(EAX));
|
||||||
|
SUB(32, R(EDX), Imm32(start - (range_size - 1)));
|
||||||
|
CMP(32, R(EDX), Imm32(size + range_size - 1));
|
||||||
|
slow_paths.push_back(J_CC(CC_B, Jump::Near));
|
||||||
|
}
|
||||||
if ((m_core.m_cp15.control & 1U) != 0)
|
if ((m_core.m_cp15.control & 1U) != 0)
|
||||||
{
|
{
|
||||||
MOV(32, R(EDX), R(EAX));
|
MOV(32, R(EDX), R(EAX));
|
||||||
|
|||||||
@@ -476,7 +476,11 @@ StarletMemory::StarletMemory(Core::System& system) : m_system(system)
|
|||||||
ERROR_LOG_FMT(IOS, "REV_WATCH: ignoring range '{}'", range);
|
ERROR_LOG_FMT(IOS, "REV_WATCH: ignoring range '{}'", range);
|
||||||
}
|
}
|
||||||
for (const auto& [start, size] : m_watch_ranges)
|
for (const auto& [start, size] : m_watch_ranges)
|
||||||
|
{
|
||||||
NOTICE_LOG_FMT(IOS, "REVWATCH range {:08x}+{:x}", start, size);
|
NOTICE_LOG_FMT(IOS, "REVWATCH range {:08x}+{:x}", start, size);
|
||||||
|
m_watch_shadow.emplace_back(size);
|
||||||
|
m_watch_known.emplace_back(size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,21 +499,116 @@ bool StarletMemory::WatchHit(u32 address, u32 size) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StarletMemory::WatchCPU(u32 address, u32 size, u32 value) const
|
void StarletMemory::WatchCPU(u32 address, u32 size, u32 value)
|
||||||
{
|
{
|
||||||
if (!WatchHit(address, size))
|
if (!WatchHit(address, size))
|
||||||
return;
|
return;
|
||||||
NOTICE_LOG_FMT(IOS, "REVWATCH CPU {:08x}/{} {:08x} PC {:08x} LR {:08x}", address, size, value,
|
NOTICE_LOG_FMT(IOS, "REVWATCH CPU {:08x}/{} {:08x} PC {:08x} LR {:08x}", address, size, value,
|
||||||
m_watch_core ? m_watch_core->GetInstructionAddress() : 0,
|
m_watch_core ? m_watch_core->GetInstructionAddress() : 0,
|
||||||
m_watch_core ? m_watch_core->GetRegister(14) : 0);
|
m_watch_core ? m_watch_core->GetRegister(14) : 0);
|
||||||
|
// The bus stores scalars big-endian.
|
||||||
|
std::array<u8, 4> bytes{};
|
||||||
|
for (u32 i = 0; i < size; ++i)
|
||||||
|
bytes[i] = static_cast<u8>(value >> (8 * (size - 1 - i)));
|
||||||
|
WatchRecord(address, bytes.data(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StarletMemory::WatchDMA(u32 address, u32 size) const
|
void StarletMemory::WatchDMA(u32 address, const u8* data, u32 size)
|
||||||
{
|
{
|
||||||
if (!WatchHit(address, size))
|
if (!WatchHit(address, size))
|
||||||
return;
|
return;
|
||||||
NOTICE_LOG_FMT(IOS, "REVWATCH DMA {:08x}/{:x} PC {:08x}", address, size,
|
NOTICE_LOG_FMT(IOS, "REVWATCH DMA {:08x}/{:x} PC {:08x}", address, size,
|
||||||
m_watch_core ? m_watch_core->GetInstructionAddress() : 0);
|
m_watch_core ? m_watch_core->GetInstructionAddress() : 0);
|
||||||
|
WatchRecord(address, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StarletMemory::WatchRecord(u32 address, const u8* data, u32 size)
|
||||||
|
{
|
||||||
|
for (size_t range = 0; range < m_watch_ranges.size(); ++range)
|
||||||
|
{
|
||||||
|
const auto [start, length] = m_watch_ranges[range];
|
||||||
|
for (u32 i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
const u32 at = address + i;
|
||||||
|
if (at >= start && at - start < length)
|
||||||
|
{
|
||||||
|
m_watch_shadow[range][at - start] = data[i];
|
||||||
|
m_watch_known[range][at - start] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StarletMemory::WatchRead(u32 address, u32 size)
|
||||||
|
{
|
||||||
|
// Phase 11 H1: a byte the Starlet reads that differs from what the Starlet or a device last
|
||||||
|
// wrote there was changed by another writer. Log it with the reader, then adopt the new value.
|
||||||
|
const u8* memory = m_system.GetMemory().GetPointerForRange(address, size);
|
||||||
|
if (memory == nullptr)
|
||||||
|
return;
|
||||||
|
for (size_t range = 0; range < m_watch_ranges.size(); ++range)
|
||||||
|
{
|
||||||
|
const auto [start, length] = m_watch_ranges[range];
|
||||||
|
u64 read = 0;
|
||||||
|
u64 expected = 0;
|
||||||
|
bool differs = false;
|
||||||
|
for (u32 i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
const u32 at = address + i;
|
||||||
|
const bool inside = at >= start && at - start < length;
|
||||||
|
const u8 shadow = inside ? m_watch_shadow[range][at - start] : memory[i];
|
||||||
|
read = (read << 8) | memory[i];
|
||||||
|
expected = (expected << 8) | shadow;
|
||||||
|
if (inside && m_watch_known[range][at - start] && shadow != memory[i])
|
||||||
|
{
|
||||||
|
differs = true;
|
||||||
|
m_watch_shadow[range][at - start] = memory[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (differs)
|
||||||
|
{
|
||||||
|
NOTICE_LOG_FMT(IOS,
|
||||||
|
"REVWATCH DIFF {:08x}/{} read {:0{}x} expected {:0{}x} PC {:08x} LR {:08x}",
|
||||||
|
address, size, read, size * 2, expected, size * 2,
|
||||||
|
m_watch_core ? m_watch_core->GetInstructionAddress() : 0,
|
||||||
|
m_watch_core ? m_watch_core->GetRegister(14) : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StarletMemory::WatchAudit()
|
||||||
|
{
|
||||||
|
// Phase 11 H1, at each IPC reply: every run of written bytes that guest memory no longer
|
||||||
|
// holds (at most 16 runs logged per reply), then adopt the new values.
|
||||||
|
u32 runs = 0;
|
||||||
|
for (size_t range = 0; range < m_watch_ranges.size(); ++range)
|
||||||
|
{
|
||||||
|
const auto [start, length] = m_watch_ranges[range];
|
||||||
|
const u8* memory = m_system.GetMemory().GetPointerForRange(start, length);
|
||||||
|
if (memory == nullptr)
|
||||||
|
continue;
|
||||||
|
auto& shadow = m_watch_shadow[range];
|
||||||
|
const auto& known = m_watch_known[range];
|
||||||
|
for (u32 i = 0; i < length;)
|
||||||
|
{
|
||||||
|
if (!known[i] || shadow[i] == memory[i])
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const u32 first = i;
|
||||||
|
while (i < length && known[i] && shadow[i] != memory[i])
|
||||||
|
{
|
||||||
|
shadow[i] = memory[i];
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (++runs <= 16)
|
||||||
|
{
|
||||||
|
NOTICE_LOG_FMT(IOS, "REVWATCH AUDIT {:08x}+{:x} now {:02x}", start + first, i - first,
|
||||||
|
memory[first]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StarletMemory::Init(const std::string& dump_directory, std::string* error)
|
bool StarletMemory::Init(const std::string& dump_directory, std::string* error)
|
||||||
@@ -1239,7 +1338,7 @@ u8 StarletMemory::ReadDMA8(u32 address) const
|
|||||||
void StarletMemory::WriteDMA8(u32 address, u8 value)
|
void StarletMemory::WriteDMA8(u32 address, u8 value)
|
||||||
{
|
{
|
||||||
if (!m_watch_ranges.empty() && !m_watch_in_dma_buffer)
|
if (!m_watch_ranges.empty() && !m_watch_in_dma_buffer)
|
||||||
WatchDMA(address, 1);
|
WatchDMA(address, &value, 1);
|
||||||
if (address >= SRAM_BASE && address < SRAM_BASE + SRAM_WINDOW_SIZE)
|
if (address >= SRAM_BASE && address < SRAM_BASE + SRAM_WINDOW_SIZE)
|
||||||
{
|
{
|
||||||
const u32 offset = address - SRAM_BASE;
|
const u32 offset = address - SRAM_BASE;
|
||||||
@@ -1292,7 +1391,7 @@ void StarletMemory::WriteDMABuffer(u32 address, std::span<const u8> buffer)
|
|||||||
if (buffer.empty())
|
if (buffer.empty())
|
||||||
return;
|
return;
|
||||||
if (!m_watch_ranges.empty())
|
if (!m_watch_ranges.empty())
|
||||||
WatchDMA(address, static_cast<u32>(buffer.size()));
|
WatchDMA(address, buffer.data(), static_cast<u32>(buffer.size()));
|
||||||
if (u8* memory = GetDMAMemoryPointer(address, buffer.size()))
|
if (u8* memory = GetDMAMemoryPointer(address, buffer.size()))
|
||||||
{
|
{
|
||||||
std::memcpy(memory, buffer.data(), buffer.size());
|
std::memcpy(memory, buffer.data(), buffer.size());
|
||||||
@@ -3611,6 +3710,8 @@ void StarletMemory::WriteRegister(u32 address, u32 value)
|
|||||||
|
|
||||||
u8 StarletMemory::Read8(u32 address)
|
u8 StarletMemory::Read8(u32 address)
|
||||||
{
|
{
|
||||||
|
if (!m_watch_ranges.empty() && WatchHit(address, 1))
|
||||||
|
WatchRead(address, 1);
|
||||||
if (IsBootROMAddress(address))
|
if (IsBootROMAddress(address))
|
||||||
return m_boot_rom[GetBootROMOffset(address)];
|
return m_boot_rom[GetBootROMOffset(address)];
|
||||||
|
|
||||||
@@ -3732,6 +3833,8 @@ u8 StarletMemory::Read8(u32 address)
|
|||||||
|
|
||||||
u16 StarletMemory::Read16(u32 address)
|
u16 StarletMemory::Read16(u32 address)
|
||||||
{
|
{
|
||||||
|
if (!m_watch_ranges.empty() && WatchHit(address, 2))
|
||||||
|
WatchRead(address, 2);
|
||||||
// Aligned reads stay inside one SRAM/boot0 mapping segment. Resolve the live mapping once,
|
// Aligned reads stay inside one SRAM/boot0 mapping segment. Resolve the live mapping once,
|
||||||
// including its holes, before considering unrelated memory and peripheral ranges.
|
// including its holes, before considering unrelated memory and peripheral ranges.
|
||||||
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
|
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
|
||||||
@@ -3766,6 +3869,8 @@ u16 StarletMemory::Read16(u32 address)
|
|||||||
|
|
||||||
u32 StarletMemory::Read32(u32 address)
|
u32 StarletMemory::Read32(u32 address)
|
||||||
{
|
{
|
||||||
|
if (!m_watch_ranges.empty() && WatchHit(address, 4))
|
||||||
|
WatchRead(address, 4);
|
||||||
// ARM IOS accesses the Hollywood timer and interrupt controller almost exclusively as aligned
|
// ARM IOS accesses the Hollywood timer and interrupt controller almost exclusively as aligned
|
||||||
// words. Falling back to ARMBus::Read32 decomposes each access into four virtual Read8 calls;
|
// words. Falling back to ARMBus::Read32 decomposes each access into four virtual Read8 calls;
|
||||||
// every byte then repeats the complete device-range decoder and IPC register switch. Preserve
|
// every byte then repeats the complete device-range decoder and IPC register switch. Preserve
|
||||||
@@ -4124,6 +4229,9 @@ void StarletMemory::Write32(u32 address, u32 value)
|
|||||||
{
|
{
|
||||||
WriteRegister(address, value);
|
WriteRegister(address, value);
|
||||||
m_system.GetWiiIPC().WriteStarletRegister(address - HW_BASE, value);
|
m_system.GetWiiIPC().WriteStarletRegister(address - HW_BASE, value);
|
||||||
|
// Phase 11 H1: an IPC reply (ARMCTRL Y1) audits the watched ranges.
|
||||||
|
if (!m_watch_ranges.empty() && address == HW_BASE + 0x0c && (value & 1) != 0)
|
||||||
|
WatchAudit();
|
||||||
if (address == HW_BASE + 0x0c && (value & 0x09) != 0)
|
if (address == HW_BASE + 0x0c && (value & 0x09) != 0)
|
||||||
{
|
{
|
||||||
if (Starlet* const starlet = m_system.GetStarlet())
|
if (Starlet* const starlet = m_system.GetStarlet())
|
||||||
|
|||||||
@@ -352,9 +352,16 @@ private:
|
|||||||
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
||||||
const ARMCore* m_watch_core = nullptr;
|
const ARMCore* m_watch_core = nullptr;
|
||||||
bool m_watch_in_dma_buffer = false;
|
bool m_watch_in_dma_buffer = false;
|
||||||
|
// Phase 11 H1: each watched range as the Starlet and the devices wrote it, and which of its
|
||||||
|
// bytes they wrote. Starlet reads and IPC replies compare guest memory with it.
|
||||||
|
std::vector<std::vector<u8>> m_watch_shadow;
|
||||||
|
std::vector<std::vector<u8>> m_watch_known;
|
||||||
bool WatchHit(u32 address, u32 size) const;
|
bool WatchHit(u32 address, u32 size) const;
|
||||||
void WatchCPU(u32 address, u32 size, u32 value) const;
|
void WatchCPU(u32 address, u32 size, u32 value);
|
||||||
void WatchDMA(u32 address, u32 size) const;
|
void WatchDMA(u32 address, const u8* data, u32 size);
|
||||||
|
void WatchRecord(u32 address, const u8* data, u32 size);
|
||||||
|
void WatchRead(u32 address, u32 size);
|
||||||
|
void WatchAudit();
|
||||||
bool m_nand_read_only = false;
|
bool m_nand_read_only = false;
|
||||||
bool m_boot0_mapped = true;
|
bool m_boot0_mapped = true;
|
||||||
bool m_sram_split_mode = false;
|
bool m_sram_split_mode = false;
|
||||||
|
|||||||
@@ -53,18 +53,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
"path": "Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||||
"sha256": "40d45aed7c5edb4540105dcdee4dd8dcde88c21ba7f3279eff0a9564f2d02a29",
|
"sha256": "262946f1fb95250d1aa1ca73c9bc27ef5f83511731bf84c765a41b04fe7ff70a",
|
||||||
"bytes": 75597
|
"bytes": 76503
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/Core/IOS/Starlet/ARMCore.h",
|
"path": "Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||||
"sha256": "765bc1ebe9554f9821fd649a94a74f27d2b811383b73286062bf382d6a15aee6",
|
"sha256": "fa80b0d697e19f0aee178be4b13097ad5466f0bbd0cb9b3e7a74876c045b731f",
|
||||||
"bytes": 14356
|
"bytes": 14647
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||||
"sha256": "2aa934e97097b25fdf760a9a97849ee066439f1ea7ad231b9329611feeb24e23",
|
"sha256": "30ac3e6f74edd40a76406394ed40d66a1ca5b6b9a17b9699a1534d255320108f",
|
||||||
"bytes": 109011
|
"bytes": 109425
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.h",
|
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.h",
|
||||||
@@ -93,13 +93,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
"path": "Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||||
"sha256": "b6896d66410e08629b1dee568f94326a1caab1fa2740ad8c886f50b4a7d494de",
|
"sha256": "f46cb37943c0feb9695e5d0541864e06fb47c2cbed7e6d48b03df64006181afd",
|
||||||
"bytes": 192641
|
"bytes": 196308
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/Core/IOS/Starlet/StarletMemory.h",
|
"path": "Source/Core/Core/IOS/Starlet/StarletMemory.h",
|
||||||
"sha256": "3005d026366a27d28e2bab0ee6cdbb9fffc347e10a384b77f19e23db1ec09ca0",
|
"sha256": "6d58d81d94b32a647dbc3babea3e943aee307c02d9f1fb8904d8ebaccd22cf05",
|
||||||
"bytes": 16146
|
"bytes": 16542
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "Source/Core/DolphinNoGUI/PlatformHeadless.cpp",
|
"path": "Source/Core/DolphinNoGUI/PlatformHeadless.cpp",
|
||||||
@@ -178,6 +178,17 @@
|
|||||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.h"
|
"Source/Core/Core/IOS/Starlet/ARMJitX64.h"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recordedAt": "2026-10-06",
|
||||||
|
"purpose": "StarletOS Phase 11 proposal H1: with REV_WATCH, a shadow of each watched range (Starlet stores and device DMA); Starlet reads that differ from it (REVWATCH DIFF) and IPC-reply audits (REVWATCH AUDIT) reveal other writers; register-list transfers whose virtual range overlaps a watched range run through the architectural helper and log their translation (REVWATCH LDM/STM)",
|
||||||
|
"files": [
|
||||||
|
"Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||||
|
"Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||||
|
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||||
|
"Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||||
|
"Source/Core/Core/IOS/Starlet/StarletMemory.h"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -193,3 +193,22 @@ table between two filesystem requests (the companion's finding 11).
|
|||||||
- The 95 selected Starlet tests passed (`.starlet_linux`); `.starlet_linux_x11`
|
- The 95 selected Starlet tests passed (`.starlet_linux`); `.starlet_linux_x11`
|
||||||
rebuilt. Smoke run with the companion's Revolution P11-H06: 1519 watched
|
rebuilt. Smoke run with the companion's Revolution P11-H06: 1519 watched
|
||||||
writes on its layer table during start-up, attributed by instruction.
|
writes on its layer table during start-up, attributed by instruction.
|
||||||
|
|
||||||
|
## Shadow comparison for the write watch — 2026-10-06
|
||||||
|
|
||||||
|
Companion proposal H1 (approved). The companion's diagnostic image showed
|
||||||
|
its kernel reading back, from its RAM layer table, an entry different from
|
||||||
|
the one it had just written, with no other Starlet store or device DMA seen.
|
||||||
|
|
||||||
|
- With `REV_WATCH`, `StarletMemory` keeps a shadow of each range, updated by
|
||||||
|
the Starlet stores and device DMA it logs. A Starlet read whose bytes differ
|
||||||
|
from the shadow logs `REVWATCH DIFF addr/size read … expected … PC … LR …`
|
||||||
|
(another writer changed them: Broadway, its GPU, the DSP…); each IPC reply
|
||||||
|
(ARMCTRL Y1) audits the ranges (`REVWATCH AUDIT addr+len now …`, at most 16
|
||||||
|
runs per reply). The shadow then adopts the new bytes.
|
||||||
|
- Register-list transfers whose virtual range overlaps a watched range now
|
||||||
|
take the JIT's architectural helper, and the interpreter logs
|
||||||
|
`REVWATCH LDM|STM virtual->physical/size PC …` (its own translation).
|
||||||
|
- `ARMCore.cpp` keeps its CRLF bytes. 95 Starlet tests passed. Companion
|
||||||
|
smoke run (P11-H06D, 150 s): no DIFF, no AUDIT, 9 LDM with identical
|
||||||
|
virtual and physical addresses.
|
||||||
Reference in new issue
Block a user