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
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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user