snapshot: preserve the uncommitted Canary instrumentation (not authored here)
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 2m0s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped

Working-tree snapshot taken 2026-07-28 so this work is not lost. To be clear
about provenance: NONE of this was written in the session that committed it —
it is pre-existing uncommitted work on phase-a-tracing, last committed 2026-07-09,
from the xenia-rs decoder/deadlock investigation. It is recorded verbatim, not
reviewed and not tested.

Contents: 24 tracked modifications (ppc_emit_memory, xex_module, object_table,
shim_utils, xboxkrnl threading/rtl, xevent/xobject/xthread, memory) plus the
untracked probe sources audit_68_host_mem_watch, audit_69_event_signal_watch,
audit_70_semaphore_release_watch, phase_b_snapshot, and cmake/toolchains.

Deliberately excluded: build-cross/ (51 GB of build output) and
vkd3d-proton.cache. Note third_party/snappy is a submodule pointer change whose
target may not be pushed anywhere, so this branch is a preservation record rather
than a guaranteed-buildable tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 20:03:33 +00:00
parent a154309022
commit 30d05ee97b
34 changed files with 2981 additions and 32 deletions

View File

@@ -14,6 +14,7 @@
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/assert.h"
#include "xenia/base/audit_68_host_mem_watch_fwd.h"
#include "xenia/base/byte_stream.h"
#include "xenia/base/clock.h"
#include "xenia/base/cvar.h"
@@ -90,6 +91,9 @@ uint32_t get_page_count(uint32_t value, uint32_t page_size) {
static Memory* active_memory_ = nullptr;
// AUDIT-068 — process-global accessor (declared in memory.h).
Memory* Memory::active() { return active_memory_; }
void CrashDump() {
static std::atomic<int> in_crash_dump(0);
if (in_crash_dump.fetch_add(1)) {
@@ -151,11 +155,41 @@ Memory::Memory() {
uint32_t(xe::memory::allocation_granularity());
assert_zero(active_memory_);
active_memory_ = this;
// AUDIT-068: register host→guest translation thunk so the watch slow path
// in xenia-base can resolve guest VAs without depending on xenia-core.
xe::audit_68::g_host_to_guest_thunk = [](const void* host_ptr) -> uint32_t {
Memory* m = active_memory_;
return m ? m->HostToGuestVirtual(host_ptr) : 0u;
};
// AUDIT-068 Session 3: register guest→host translation thunk and a
// page-protect query thunk for the read-mode probe. The probe thread uses
// QueryProtect to skip unmapped/uncommitted pages before dereferencing.
xe::audit_68::g_guest_to_host_thunk = [](uint32_t va) -> const void* {
Memory* m = active_memory_;
return m ? reinterpret_cast<const void*>(m->TranslateVirtual(va))
: nullptr;
};
xe::audit_68::g_query_protect_thunk = [](uint32_t va,
uint32_t* out_protect) -> bool {
Memory* m = active_memory_;
if (!m) return false;
BaseHeap* heap = m->LookupHeap(va);
if (!heap) {
if (out_protect) *out_protect = 0;
return false;
}
return heap->QueryProtect(va, out_protect);
};
}
Memory::~Memory() {
assert_true(active_memory_ == this);
active_memory_ = nullptr;
xe::audit_68::g_host_to_guest_thunk = nullptr;
xe::audit_68::g_guest_to_host_thunk = nullptr;
xe::audit_68::g_query_protect_thunk = nullptr;
// Uninstall the MMIO handler, as we won't be able to service more
// requests.
@@ -540,16 +574,71 @@ uint32_t Memory::GetPhysicalAddress(uint32_t address) const {
}
void Memory::Zero(uint32_t address, uint32_t size) {
// AUDIT-068: log a single span event with value=0; size is capped at 8 for
// the value field. Slow path is gated on the atomic flag.
xe::audit_68::check_guest_va(address, 0,
static_cast<uint8_t>(std::min<uint32_t>(size, 8)),
"Memory::Zero");
std::memset(TranslateVirtual(address), 0, size);
}
void Memory::Fill(uint32_t address, uint32_t size, uint8_t value) {
// Replicate the fill byte across the value field so value_matches can
// recognise e.g. 0xDEADBEEF only if the byte is 0xDE/0xAD/0xBE/0xEF — for
// capture purposes the byte itself in the low slot is enough.
uint64_t v = static_cast<uint64_t>(value);
v |= v << 8;
v |= v << 16;
v |= v << 32;
xe::audit_68::check_guest_va(address, v,
static_cast<uint8_t>(std::min<uint32_t>(size, 8)),
"Memory::Fill");
std::memset(TranslateVirtual(address), value, size);
}
void Memory::Copy(uint32_t dest, uint32_t src, uint32_t size) {
uint8_t* pdest = TranslateVirtual(dest);
const uint8_t* psrc = TranslateVirtual(src);
// AUDIT-068 Session 2: full byte-scan over 4-byte aligned positions of the
// source buffer. Catches XEX-loader-style memcpys where a vptr (the target
// u32 value) is buried somewhere mid-buffer rather than at offset 0. Cost
// O(size/4 * N_values) with N_values capped at 8 inside value_matches —
// negligible vs the underlying memcpy throughput.
//
// Gated on active bit 0x1 (values-mode) AND active != 0. If only addrs are
// configured (Run 2 voice-struct mode), we still emit a single addr-only
// event covering the destination span so addr-watch isn't broken.
uint32_t active = xe::audit_68::g_active.load(std::memory_order_relaxed);
if (active != 0) [[unlikely]] {
if ((active & 0x1) && size >= 4) {
// Scan source for any configured u32 value (big-endian, mirrors how
// guest sees the bytes). 4-byte aligned offsets only.
uint32_t aligned_end = size & ~3u;
for (uint32_t i = 0; i < aligned_end; i += 4) {
uint32_t be_u32 =
(uint32_t(psrc[i + 0]) << 24) | (uint32_t(psrc[i + 1]) << 16) |
(uint32_t(psrc[i + 2]) << 8) | uint32_t(psrc[i + 3]);
xe::audit_68::check_guest_va(dest + i, be_u32, 4, "Memory::Copy");
}
}
if (active & 0x2) {
// Addr-only mode: emit a single coarse event tagged with the dest base
// and first u32 of source for context. The slow-path range check will
// log iff the dest span intersects a configured addr range.
uint64_t v = 0;
if (size >= 4) {
v = (uint64_t(psrc[0]) << 24) | (uint64_t(psrc[1]) << 16) |
(uint64_t(psrc[2]) << 8) | uint64_t(psrc[3]);
} else if (size > 0) {
for (uint32_t i = 0; i < size; ++i) {
v = (v << 8) | psrc[i];
}
}
xe::audit_68::check_guest_va(
dest, v, static_cast<uint8_t>(std::min<uint32_t>(size, 8)),
"Memory::Copy");
}
}
std::memcpy(pdest, psrc, size);
}