diff --git a/src/xenia/base/memory.h b/src/xenia/base/memory.h index 8ef40bbff..e78c8499c 100644 --- a/src/xenia/base/memory.h +++ b/src/xenia/base/memory.h @@ -18,6 +18,7 @@ #include #include +#include "xenia/base/audit_68_host_mem_watch_fwd.h" #include "xenia/base/byte_order.h" namespace xe { @@ -354,34 +355,52 @@ template void store(void* mem, const T& value); template <> inline void store(void* mem, const int8_t& value) { + xe::audit_68::check_host_write(mem, static_cast( + static_cast(value)), + 1, "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const uint8_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 1, + "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const int16_t& value) { + xe::audit_68::check_host_write(mem, static_cast( + static_cast(value)), + 2, "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const uint16_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 2, + "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const int32_t& value) { + xe::audit_68::check_host_write(mem, static_cast( + static_cast(value)), + 4, "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const uint32_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 4, + "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const int64_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 8, + "store"); *reinterpret_cast(mem) = value; } template <> inline void store(void* mem, const uint64_t& value) { + xe::audit_68::check_host_write(mem, value, 8, "store"); *reinterpret_cast(mem) = value; } template <> @@ -411,34 +430,52 @@ template void store_and_swap(void* mem, const T& value); template <> inline void store_and_swap(void* mem, const int8_t& value) { + xe::audit_68::check_host_write(mem, static_cast( + static_cast(value)), + 1, "store_and_swap"); *reinterpret_cast(mem) = value; } template <> inline void store_and_swap(void* mem, const uint8_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 1, + "store_and_swap"); *reinterpret_cast(mem) = value; } template <> inline void store_and_swap(void* mem, const int16_t& value) { + xe::audit_68::check_host_write(mem, static_cast( + static_cast(value)), + 2, "store_and_swap"); *reinterpret_cast(mem) = byte_swap(value); } template <> inline void store_and_swap(void* mem, const uint16_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 2, + "store_and_swap"); *reinterpret_cast(mem) = byte_swap(value); } template <> inline void store_and_swap(void* mem, const int32_t& value) { + xe::audit_68::check_host_write(mem, static_cast( + static_cast(value)), + 4, "store_and_swap"); *reinterpret_cast(mem) = byte_swap(value); } template <> inline void store_and_swap(void* mem, const uint32_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 4, + "store_and_swap"); *reinterpret_cast(mem) = byte_swap(value); } template <> inline void store_and_swap(void* mem, const int64_t& value) { + xe::audit_68::check_host_write(mem, static_cast(value), 8, + "store_and_swap"); *reinterpret_cast(mem) = byte_swap(value); } template <> inline void store_and_swap(void* mem, const uint64_t& value) { + xe::audit_68::check_host_write(mem, value, 8, "store_and_swap"); *reinterpret_cast(mem) = byte_swap(value); } template <> diff --git a/src/xenia/cpu/cpu_flags.cc b/src/xenia/cpu/cpu_flags.cc index 3ff067e15..705ad060b 100644 --- a/src/xenia/cpu/cpu_flags.cc +++ b/src/xenia/cpu/cpu_flags.cc @@ -57,3 +57,76 @@ DEFINE_bool(break_condition_truncate, true, "truncate value to 32-bits", "CPU"); DEFINE_bool(break_on_debugbreak, true, "int3 on JITed __debugbreak requests.", "CPU"); + +// AUDIT-DEMO: smoke marker (memory entry: emulator.cc:225,283). Always-on bool. +DEFINE_bool(audit_demo_setup_trace, true, + "Audit smoke marker: log AUDIT-DEMO-SETUP-BEGIN at emulator setup.", + "Audit"); + +// AUDIT-061: comma-separated list of guest PCs to log on each fire. +// Format: "0xPC1,0xPC2,..." (max 32 PCs). Each fire emits +// AUDIT-061-BR pc=X lr=X cr0=LGE cr6=LGE r3=X r4=X r5=X r6=X r31=X tid=N. +// Default empty (off); no perf cost when empty. +DEFINE_string(audit_61_branch_probe_pcs, "", + "AUDIT-061: CSV of guest PCs to trace (cr0/cr6 + regs/tid).", + "Audit"); + +// AUDIT-067: comma-separated list of u32 values to watch. When non-empty, +// every 4-byte guest store (stw/stwu/stwx/stwux/stmw) emits a runtime +// equality check; matches log AUDIT-067-VAL pc=X lr=X val=X dst=X r3..r6 r31 tid=N. +// Max 4 values. Default empty (off); zero overhead when empty. +DEFINE_string(audit_67_value_watch, "", + "AUDIT-067: CSV of u32 values (max 4) — log every guest " + "store whose value matches.", + "Audit"); + +// AUDIT-068: host-side memory-write watch. See cpu_flags.h header for format. +// Mirrors AUDIT-067 but covers host-side writes (xe::store_and_swap, +// Memory::Zero/Fill/Copy). Empty default = zero cost. +DEFINE_string(audit_68_host_mem_watch_values, "", + "AUDIT-068: CSV of u32 values (max 8) — log every host-side " + "guest-memory write whose value matches.", + "Audit"); +DEFINE_string(audit_68_host_mem_watch_addrs, "", + "AUDIT-068: CSV of guest VAs or VA ranges 'START-END' (max 8) " + "— log every host-side guest-memory write whose guest VA falls " + "within the configured set.", + "Audit"); + +// Phase A — see kernel/event_log.h. +DEFINE_string(phase_a_event_log_path, "", + "Phase A: write schema-v1 JSONL event log to this path. " + "Empty (default) = disabled.", + "Audit"); +DEFINE_bool(phase_a_event_log_mem_writes, false, + "Phase A: include mem.write events in the JSONL log. RESERVED — " + "not wired in this phase. Default false.", + "Audit"); + +// Phase D Stage 1 — see kernel/event_log.h `EmitContentionObserved`. +DEFINE_bool(kernel_emit_contention, false, + "Phase D Stage 1: emit `contention.observed` events when " + "RtlEnterCriticalSection's spin loop is exhausted and the call " + "falls through to xeKeWaitForSingleObject. Default false (zero " + "cost when disabled). Requires --phase_a_event_log_path to be " + "set as well.", + "Audit"); + +// Phase B — see kernel/phase_b_snapshot.h. +DEFINE_string(phase_b_snapshot_dir, "", + "Phase B: write 5-file structured state snapshot to " + "/canary/ at the moment immediately before the first " + "guest PPC instruction of entry_point. Empty (default) = " + "disabled, zero overhead.", + "Audit"); +DEFINE_bool(phase_b_snapshot_and_exit, false, + "Phase B: after writing the snapshot, exit the process " + "immediately (std::_Exit(0)) so re-runs are byte-deterministic.", + "Audit"); +DEFINE_bool(phase_b_dump_section_content, false, + "Phase B: in memory.json, populate section_contents[].content_b64 " + "with raw bytes of every committed XEX-image region. Default " + "false — per-region SHA-256 is enough for the routine diff; " + "this is the escape hatch for the STOP-and-report condition " + "(image_loaded_sha256 mismatch).", + "Audit"); diff --git a/src/xenia/cpu/cpu_flags.h b/src/xenia/cpu/cpu_flags.h index 38c4f98ba..2b1e1fd9c 100644 --- a/src/xenia/cpu/cpu_flags.h +++ b/src/xenia/cpu/cpu_flags.h @@ -35,4 +35,45 @@ DECLARE_bool(break_condition_truncate); DECLARE_bool(break_on_debugbreak); +// AUDIT-DEMO smoke marker. +DECLARE_bool(audit_demo_setup_trace); + +// AUDIT-061: multi-PC branch probe — emits one log line per fire with +// (pc, lr, cr0 LGE, cr6 LGE, r3, r4, r5, r6, r31, tid). CSV of guest PCs. +DECLARE_string(audit_61_branch_probe_pcs); + +// AUDIT-067: value-watch — emit a log line for each 32-bit guest store whose +// value-to-be-stored matches any configured value. CSV of u32 values +// ("0xDEADBEEF,..."), max 4 entries. Default empty (off); zero cost when empty. +DECLARE_string(audit_67_value_watch); + +// AUDIT-068: host-side memory-write watch — emit a log line for each host-side +// write to guest memory whose VALUE matches any configured u32 value, or whose +// guest VA falls within any configured ADDR or ADDR-range. Mirrors AUDIT-067 +// but covers the host-side write paths (xe::store_and_swap, Memory::Zero/ +// Fill/Copy) that AUDIT-067's JIT store-opcode hooks cannot see. +// +// VALUES: CSV of u32 values, max 8 entries; e.g. "0x8200A208,0x8200A928". +// ADDRS: CSV of guest VAs or VA ranges, max 8 entries; range form is +// "0xSTART-0xEND" (inclusive). e.g. "0x42500000-0x42600000,0xBCE25340". +// Default empty (off); zero cost on the hot path when both are empty. +DECLARE_string(audit_68_host_mem_watch_values); +DECLARE_string(audit_68_host_mem_watch_addrs); + +// Phase A: JSONL event-log emitter path. When non-empty, the engine writes +// schema-v1 JSONL events to this file. Empty (default) = no overhead, no +// behavior change. Schema: xenia-rs/audit-runs/phase-a-diff-harness/schema-v1.md +DECLARE_string(phase_a_event_log_path); +DECLARE_bool(phase_a_event_log_mem_writes); + +// Phase B: initial-state snapshot. When the dir cvar is non-empty, the +// engine writes a five-file structured state snapshot (cpu_state.json, +// memory.json, kernel.json, vfs.json, config.json, plus manifest.json) to +// `/canary/` at the moment immediately before the first guest PPC +// instruction of the XEX entry_point executes. See +// `xenia-rs/audit-runs/phase-b-state-equivalence/`. +DECLARE_string(phase_b_snapshot_dir); +DECLARE_bool(phase_b_snapshot_and_exit); +DECLARE_bool(phase_b_dump_section_content); + #endif // XENIA_CPU_CPU_FLAGS_H_ diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index 22ba66aee..571b424f5 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -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 in_crash_dump(0); if (in_crash_dump.fetch_add(1)) { @@ -151,11 +155,19 @@ 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; + }; } Memory::~Memory() { assert_true(active_memory_ == this); active_memory_ = nullptr; + xe::audit_68::g_host_to_guest_thunk = nullptr; // Uninstall the MMIO handler, as we won't be able to service more // requests. @@ -540,16 +552,48 @@ 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(std::min(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(value); + v |= v << 8; + v |= v << 16; + v |= v << 32; + xe::audit_68::check_guest_va(address, v, + static_cast(std::min(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); + // We don't know the data without scanning; just log the destination span + + // first u32 of the source as a value hint. Slow path is gated. + if (xe::audit_68::g_active.load(std::memory_order_relaxed) != 0) [[unlikely]] { + uint64_t v = 0; + if (size >= 4) { + // Read big-endian u32 from the source (mirrors how guest sees it). + 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(std::min(size, 8)), + "Memory::Copy"); + } std::memcpy(pdest, psrc, size); } diff --git a/src/xenia/memory.h b/src/xenia/memory.h index bd9519a40..fa712fe08 100644 --- a/src/xenia/memory.h +++ b/src/xenia/memory.h @@ -347,6 +347,13 @@ class Memory { Memory(); ~Memory(); + // AUDIT-068: process-global Memory singleton accessor. Returns the + // currently-constructed Memory instance, or nullptr if none. Set inside + // Memory::Memory()/~Memory(); see memory.cc `active_memory_`. Used by + // xe::audit_68::check_host_write() to translate a host pointer back to a + // guest VA without an explicit Memory* context. + static Memory* active(); + // Initializes the memory system. // This may fail if the host address space could not be reserved or the // mapping to the file system fails. === NEW FILE src/xenia/base/audit_68_host_mem_watch_fwd.h === /** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * AUDIT-068: host-side memory-write watch — forward declarations only. * * Declarations here are intentionally minimal so that xenia/base/memory.h can * include this without pulling in xenia/memory.h (which would create a * circular dependency: xenia-base → xenia-core → xenia-base). The full * definitions live in xenia/audit_68_host_mem_watch.{h,cc} (xenia-core). * * Hot path: callers (the integer specializations of xe::store_and_swap) * load the atomic flag once. When it is 0 (default), no further work is done * — a single relaxed atomic load and a predictable branch. ****************************************************************************** */ #ifndef XENIA_BASE_AUDIT_68_HOST_MEM_WATCH_FWD_H_ #define XENIA_BASE_AUDIT_68_HOST_MEM_WATCH_FWD_H_ #include #include namespace xe { namespace audit_68 { // 0 = inactive (default). Non-zero = the cvars have been parsed and at least // one watch is configured. Set lazily by check_host_write_slowpath() on first // call after cvar parsing. Loaded relaxed on the hot path. // // Implementation lives in xenia-base (audit_68_host_mem_watch_base.cc) so // that callers in xenia-base/xenia-cpu/xenia-kernel can resolve the symbol // without depending on xenia-core link order. extern std::atomic g_active; // Host-pointer → guest-VA translation thunk. xenia/memory.cc::Memory::Memory() // registers a function pointer here that wraps Memory::HostToGuestVirtual. // Until set, the slow path falls back to logging the raw host pointer. using HostToGuestThunk = uint32_t (*)(const void*); extern HostToGuestThunk g_host_to_guest_thunk; // Slow path. Only invoked when g_active is non-zero. Implementation in // xenia/base/audit_68_host_mem_watch_base.cc (xenia-base). // // host_ptr: the host pointer being written (from store_and_swap's `mem`). // value: the value being stored (zero-extended to u64). // size: 1, 2, 4 or 8. // tag: caller-provided tag string (e.g. "store_and_swap"). Logged // verbatim, no formatting. Must be a static string (lifetime // beyond this call). void check_host_write_slowpath(const void* host_ptr, uint64_t value, uint8_t size, const char* tag); // Same as above, but with a known guest VA (for callers like Memory::Zero/ // Fill/Copy that have the VA but not a single host pointer). void check_guest_va_slowpath(uint32_t guest_va, uint64_t value, uint8_t size, const char* tag); // Inline hot-path wrappers. Single relaxed atomic load + branch when inactive. inline void check_host_write(const void* host_ptr, uint64_t value, uint8_t size, const char* tag) { if (g_active.load(std::memory_order_relaxed) != 0) [[unlikely]] { check_host_write_slowpath(host_ptr, value, size, tag); } } inline void check_guest_va(uint32_t guest_va, uint64_t value, uint8_t size, const char* tag) { if (g_active.load(std::memory_order_relaxed) != 0) [[unlikely]] { check_guest_va_slowpath(guest_va, value, size, tag); } } } // namespace audit_68 } // namespace xe #endif // XENIA_BASE_AUDIT_68_HOST_MEM_WATCH_FWD_H_ === NEW FILE src/xenia/base/audit_68_host_mem_watch_base.cc === /** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * AUDIT-068 host-side memory-write watch — implementation (xenia-base). * * Mirrors AUDIT-067 in spirit (value-CSV cvar, lazy parse, atomic-bool * activation) but observes the HOST-side write paths instead of the JIT'd * guest store opcodes. Captures writes performed by xe::store_and_swap * (xenia/base/memory.h) and by Memory::Zero/Fill/Copy (xenia/memory.cc). * * Lives in xenia-base so that the slow-path symbols resolve for callers in * xenia-base / xenia-cpu / xenia-kernel without depending on xenia-core link * order. The host→guest VA translation is provided by a function-pointer * thunk that xenia::Memory::Memory() registers at construction. * * See xenia/base/audit_68_host_mem_watch_fwd.h for the API. * See xenia/cpu/cpu_flags.{h,cc} for the cvars. ****************************************************************************** */ #include "xenia/base/audit_68_host_mem_watch_fwd.h" #include #include #include #include #include #include #include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/base/threading.h" // We need the cvars but cpu_flags.h lives in xenia-cpu. To avoid an upward // dep we re-declare them here with the same macros — cvar.h's DECLARE_* // macros are header-safe (just `extern` declarations) and resolve against the // definitions in xenia-cpu/cpu_flags.cc at link time. (xenia-cpu links AFTER // xenia-base in the executable; symbols in xenia-cpu/cpu_flags.cc are still // resolvable from xenia-base translation units because the lld pass folds // all libraries together at the executable level.) DECLARE_string(audit_68_host_mem_watch_values); DECLARE_string(audit_68_host_mem_watch_addrs); namespace xe { namespace audit_68 { // Hot-path flag (declared in fwd header). Initial sentinel UINT32_MAX means // "unparsed"; the very first slow-path call invokes ensure_parsed() which // replaces the sentinel with the actual active bitmask (0 if both cvars are // empty, 1/2/3 otherwise). After that, hot-path calls observe the real value // and bail out cheaply when off. std::atomic g_active{0xFFFFFFFFu}; // Host→guest VA translation thunk (declared in fwd header). Set by // xenia::Memory::Memory() at construction; reset to nullptr by ~Memory(). HostToGuestThunk g_host_to_guest_thunk{nullptr}; namespace { constexpr size_t kMaxValues = 8; constexpr size_t kMaxAddrRanges = 8; struct AddrRange { uint32_t start; // inclusive uint32_t end; // inclusive }; std::vector g_values; std::vector g_addrs; std::once_flag g_parsed_flag; std::chrono::steady_clock::time_point g_t0; std::once_flag g_t0_once; int64_t host_ns_since_start() { std::call_once(g_t0_once, []() { g_t0 = std::chrono::steady_clock::now(); }); return std::chrono::duration_cast( std::chrono::steady_clock::now() - g_t0) .count(); } void trim(std::string& s) { while (!s.empty() && (s.front() == ' ' || s.front() == '\t')) { s.erase(s.begin()); } while (!s.empty() && (s.back() == ' ' || s.back() == '\t')) { s.pop_back(); } } bool parse_u32(const std::string& tok, uint32_t* out) { try { *out = static_cast(std::stoul(tok, nullptr, 0)); return true; } catch (...) { return false; } } void parse_values_csv(const std::string& csv) { size_t pos = 0; while (pos < csv.size() && g_values.size() < kMaxValues) { size_t end = csv.find(',', pos); std::string tok = csv.substr(pos, end - pos); trim(tok); if (!tok.empty()) { uint32_t v; if (parse_u32(tok, &v)) { g_values.push_back(v); } } if (end == std::string::npos) break; pos = end + 1; } } void parse_addrs_csv(const std::string& csv) { size_t pos = 0; while (pos < csv.size() && g_addrs.size() < kMaxAddrRanges) { size_t end = csv.find(',', pos); std::string tok = csv.substr(pos, end - pos); trim(tok); if (!tok.empty()) { size_t dash = tok.find('-', 2); // skip leading "0x" if present AddrRange r{}; if (dash != std::string::npos) { std::string s = tok.substr(0, dash); std::string e = tok.substr(dash + 1); trim(s); trim(e); uint32_t a, b; if (parse_u32(s, &a) && parse_u32(e, &b)) { r.start = a; r.end = b; g_addrs.push_back(r); } } else { uint32_t a; if (parse_u32(tok, &a)) { r.start = a; r.end = a + 7; g_addrs.push_back(r); } } } if (end == std::string::npos) break; pos = end + 1; } } void parse_locked() { parse_values_csv(cvars::audit_68_host_mem_watch_values); parse_addrs_csv(cvars::audit_68_host_mem_watch_addrs); uint32_t bits = 0; if (!g_values.empty()) bits |= 0x1; if (!g_addrs.empty()) bits |= 0x2; g_active.store(bits, std::memory_order_release); XELOGI( "AUDIT-068-INIT values_csv=\"{}\" addrs_csv=\"{}\" values_parsed={} " "addr_ranges_parsed={} active=0x{:X}", cvars::audit_68_host_mem_watch_values, cvars::audit_68_host_mem_watch_addrs, g_values.size(), g_addrs.size(), bits); for (size_t i = 0; i < g_values.size(); ++i) { XELOGI("AUDIT-068-INIT value[{}] = 0x{:08X}", i, g_values[i]); } for (size_t i = 0; i < g_addrs.size(); ++i) { XELOGI("AUDIT-068-INIT addr_range[{}] = 0x{:08X}-0x{:08X}", i, g_addrs[i].start, g_addrs[i].end); } } bool value_matches(uint64_t value, uint8_t size) { for (uint32_t v : g_values) { if (size >= 4 && static_cast(value) == v) return true; if (size == 8 && static_cast(value >> 32) == v) return true; if (size == 2 && (v & 0xFFFF) == (value & 0xFFFF)) return true; if (size == 1 && (v & 0xFF) == (value & 0xFF)) return true; } return false; } bool addr_matches(uint32_t guest_va, uint8_t size) { uint32_t lo = guest_va; uint32_t hi = guest_va + (size ? size - 1 : 0); for (const auto& r : g_addrs) { if (lo <= r.end && hi >= r.start) return true; } return false; } uint32_t current_tid() { return xe::threading::current_thread_id(); } void emit(uint32_t guest_va, const void* host_ptr, uint64_t value, uint8_t size, const char* tag) { XELOGI( "AUDIT-068-HOST-WRITE guest_va=0x{:08X} host_ptr=0x{:016X} " "val=0x{:016X} sz={} fn={} host_ns={} tid={}", guest_va, reinterpret_cast(host_ptr), value, static_cast(size), tag ? tag : "", host_ns_since_start(), current_tid()); } } // namespace void ensure_parsed() { std::call_once(g_parsed_flag, parse_locked); } void check_host_write_slowpath(const void* host_ptr, uint64_t value, uint8_t size, const char* tag) { ensure_parsed(); uint32_t active = g_active.load(std::memory_order_acquire); if (active == 0) return; uint32_t guest_va = 0; HostToGuestThunk thunk = g_host_to_guest_thunk; if (thunk) { guest_va = thunk(host_ptr); } bool hit = false; if ((active & 0x1) && value_matches(value, size)) hit = true; if (!hit && (active & 0x2) && thunk && addr_matches(guest_va, size)) { hit = true; } if (!hit) return; emit(guest_va, host_ptr, value, size, tag); } void check_guest_va_slowpath(uint32_t guest_va, uint64_t value, uint8_t size, const char* tag) { ensure_parsed(); uint32_t active = g_active.load(std::memory_order_acquire); if (active == 0) return; bool hit = false; if ((active & 0x1) && value_matches(value, size)) hit = true; if (!hit && (active & 0x2) && addr_matches(guest_va, size)) hit = true; if (!hit) return; emit(guest_va, nullptr, value, size, tag); } } // namespace audit_68 } // namespace xe