diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index 5da8f6adc..cbac9826c 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -13,6 +13,8 @@ #include #include +#include +#include #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" @@ -63,6 +65,76 @@ DEFINE_bool(instrument_call_times, false, "Compute time taken for functions, for profiling guest code", "x64"); #endif + +// AUDIT-061/067: forward decls of probe/watch tables (defined in +// ppc_hir_builder.cc). +namespace xe { +namespace cpu { +namespace audit61 { +const std::vector& pcs(); +} // namespace audit61 +namespace audit67 { +const std::vector& vals(); +} // namespace audit67 +} // namespace cpu +} // namespace xe + +// AUDIT-061: handler for trap codes [200, 232). arg0 carries trap idx +// (trap_code - 200), mapping to ::xe::cpu::audit61::pcs()[idx]. Emits one +// log line per fire with cr0/cr6 LGE flags + key GPRs + LR + tid. +static uint64_t TrapAudit61Branch(void* raw_context, uint64_t idx) { + auto* ctx = reinterpret_cast(raw_context); + const auto& pcs = ::xe::cpu::audit61::pcs(); + uint32_t pc = (idx < pcs.size()) ? pcs[static_cast(idx)] : 0u; + uint32_t tid = 0; + if (ctx->thread_state) { + tid = ctx->thread_state->thread_id(); + } + auto enc = [](uint8_t lt, uint8_t gt, uint8_t eq) { + char buf[4]; + buf[0] = lt ? 'L' : '.'; + buf[1] = gt ? 'G' : '.'; + buf[2] = eq ? 'E' : '.'; + buf[3] = '\0'; + return std::string(buf); + }; + XELOGI( + "AUDIT-061-BR pc={:08X} lr={:08X} cr0={} cr6={} r3={:08X} r4={:08X} " + "r5={:08X} r6={:08X} r31={:08X} tid={}", + pc, static_cast(ctx->lr), + enc(ctx->cr0.cr0_lt, ctx->cr0.cr0_gt, ctx->cr0.cr0_eq), + enc(ctx->cr6.cr6_all_equal, ctx->cr6.cr6_1, ctx->cr6.cr6_none_equal), + static_cast(ctx->r[3]), static_cast(ctx->r[4]), + static_cast(ctx->r[5]), static_cast(ctx->r[6]), + static_cast(ctx->r[31]), tid); + return 0; +} + +// AUDIT-067: handler for trap codes [250, 254). arg0 carries trap idx +// (trap_code - 250), mapping to ::xe::cpu::audit67::vals()[idx]. Fired when +// a 4-byte guest store sees the configured value. The store-emit site stashed +// (pc << 32) | (ea & 0xFFFFFFFF) into ctx->scratch right before the trap. +static uint64_t TrapAudit67ValueWatch(void* raw_context, uint64_t idx) { + auto* ctx = reinterpret_cast(raw_context); + const auto& vals = ::xe::cpu::audit67::vals(); + uint32_t val = + (idx < vals.size()) ? vals[static_cast(idx)] : 0u; + uint32_t pc = static_cast(ctx->scratch >> 32); + uint32_t dst = static_cast(ctx->scratch & 0xFFFFFFFFu); + uint32_t tid = 0; + if (ctx->thread_state) { + tid = ctx->thread_state->thread_id(); + } + XELOGI( + "AUDIT-067-VAL pc={:08X} lr={:08X} val={:08X} dst={:08X} " + "r3={:08X} r4={:08X} r5={:08X} r6={:08X} r31={:08X} tid={}", + pc, static_cast(ctx->lr), val, dst, + static_cast(ctx->r[3]), static_cast(ctx->r[4]), + static_cast(ctx->r[5]), static_cast(ctx->r[6]), + static_cast(ctx->r[31]), tid); + return 0; +} + namespace xe { namespace cpu { namespace backend { @@ -455,6 +527,20 @@ void X64Emitter::Trap(uint16_t trap_type) { // ? break; default: + // AUDIT-067: trap codes [250, 254) dispatch the value-watch handler. + // arg0 = idx into ::xe::cpu::audit67::vals(). + if (trap_type >= 250 && trap_type < 254) { + CallNative(::TrapAudit67ValueWatch, + static_cast(trap_type - 250)); + break; + } + // AUDIT-061: trap codes [200, 232) dispatch the branch-probe handler. + // arg0 = idx into ::xe::cpu::audit61::pcs(). + if (trap_type >= 200 && trap_type < 232) { + CallNative(::TrapAudit61Branch, + static_cast(trap_type - 200)); + break; + } XELOGW("Unknown trap type {}", trap_type); db(0xCC); break; diff --git a/src/xenia/cpu/cpu_flags.cc b/src/xenia/cpu/cpu_flags.cc index 3ff067e15..f78dad157 100644 --- a/src/xenia/cpu/cpu_flags.cc +++ b/src/xenia/cpu/cpu_flags.cc @@ -57,3 +57,25 @@ 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"); diff --git a/src/xenia/cpu/cpu_flags.h b/src/xenia/cpu/cpu_flags.h index 38c4f98ba..5f52647b5 100644 --- a/src/xenia/cpu/cpu_flags.h +++ b/src/xenia/cpu/cpu_flags.h @@ -35,4 +35,16 @@ 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); + #endif // XENIA_CPU_CPU_FLAGS_H_ diff --git a/src/xenia/cpu/ppc/ppc_emit_altivec.cc b/src/xenia/cpu/ppc/ppc_emit_altivec.cc index 513b21391..c9af025ff 100644 --- a/src/xenia/cpu/ppc/ppc_emit_altivec.cc +++ b/src/xenia/cpu/ppc/ppc_emit_altivec.cc @@ -9,12 +9,28 @@ #include "xenia/cpu/ppc/ppc_emit-private.h" +#include #include "xenia/base/assert.h" +#include "xenia/cpu/cpu_flags.h" #include "xenia/cpu/ppc/ppc_context.h" #include "xenia/cpu/ppc/ppc_hir_builder.h" #include +// AUDIT-067: forward-decls. Defined in ppc_emit_memory.cc / ppc_hir_builder.cc. +namespace xe { +namespace cpu { +namespace audit67 { +const std::vector& vals(); +} +namespace ppc { +void EmitAudit67ValueWatchVec(PPCHIRBuilder& f, uint32_t pc, + ::xe::cpu::hir::Value* vec128, + ::xe::cpu::hir::Value* ea); +} +} +} + namespace xe { namespace cpu { namespace ppc { @@ -175,6 +191,21 @@ int InstrEmit_stvewx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd, f.Shr(f.And(f.Truncate(ea, INT8_TYPE), f.LoadConstantUint8(0xF)), 2); Value* v = f.Extract(f.LoadVR(vd), el, INT32_TYPE); f.Store(ea, f.ByteSwap(v)); + if (!::xe::cpu::audit67::vals().empty()) { + // For stvewx: only one lane is actually stored; piggyback on the scalar + // value-watch helper by emitting the equivalent of stw of v at ea. + Value* pc_hi64 = + f.LoadConstantUint64(static_cast(i.address) << 32); + Value* ea_lo64 = f.ZeroExtend(f.Truncate(ea, INT32_TYPE), INT64_TYPE); + Value* packed = f.Or(pc_hi64, ea_lo64); + const auto& vals = ::xe::cpu::audit67::vals(); + for (size_t idx = 0; idx < vals.size(); ++idx) { + Value* cmp = f.CompareEQ(v, f.LoadConstantUint32(vals[idx])); + f.StoreContext(offsetof(::xe::cpu::ppc::PPCContext, scratch), packed); + f.ContextBarrier(); + f.TrapTrue(cmp, static_cast(250 + idx)); + } + } return 0; } int InstrEmit_stvewx(PPCHIRBuilder& f, const InstrData& i) { @@ -187,7 +218,11 @@ int InstrEmit_stvewx128(PPCHIRBuilder& f, const InstrData& i) { int InstrEmit_stvx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd, uint32_t ra, uint32_t rb) { Value* ea = f.And(CalculateEA_0(f, ra, rb), f.LoadConstantUint64(~0xFull)); - f.Store(ea, f.ByteSwap(f.LoadVR(vd))); + Value* vec = f.LoadVR(vd); + f.Store(ea, f.ByteSwap(vec)); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatchVec(f, i.address, vec, ea); + } return 0; } int InstrEmit_stvx(PPCHIRBuilder& f, const InstrData& i) { diff --git a/src/xenia/cpu/ppc/ppc_emit_memory.cc b/src/xenia/cpu/ppc/ppc_emit_memory.cc index b4bdabb49..a6b44697d 100644 --- a/src/xenia/cpu/ppc/ppc_emit_memory.cc +++ b/src/xenia/cpu/ppc/ppc_emit_memory.cc @@ -10,11 +10,22 @@ #include "xenia/cpu/ppc/ppc_emit-private.h" #include +#include #include "xenia/base/assert.h" #include "xenia/base/cvar.h" +#include "xenia/cpu/cpu_flags.h" #include "xenia/cpu/ppc/ppc_context.h" #include "xenia/cpu/ppc/ppc_hir_builder.h" +// AUDIT-067: forward-decl of value-watch table (defined in ppc_hir_builder.cc). +namespace xe { +namespace cpu { +namespace audit67 { +const std::vector& vals(); +} // namespace audit67 +} // namespace cpu +} // namespace xe + DEFINE_bool( disable_prefetch_and_cachecontrol, true, "Disables translating ppc prefetch/cache flush instructions to host " @@ -67,6 +78,90 @@ void StoreEA(PPCHIRBuilder& f, uint32_t rt, Value* ea) { f.StoreGPR(rt, ea); } +// AUDIT-067: emit a runtime equality check on the 32-bit value-to-be-stored +// against each configured watch value. On match, store (pc, EA) packed into +// the PPCContext scratch field so the native trap handler can read them, +// then fire a trap with code (kTrapBase + idx). Done host-side as a +// build-time pc constant + a runtime EA truncate, packed as +// (pc << 32) | (ea & 0xFFFFFFFF) so the handler can decompose. +static void EmitAudit67ValueWatch(PPCHIRBuilder& f, uint32_t pc, Value* val32, + Value* ea) { + const auto& vals = ::xe::cpu::audit67::vals(); + if (vals.empty()) return; + // pc is known at JIT time → emit as constant; ea is runtime. + Value* pc_hi64 = f.LoadConstantUint64(static_cast(pc) << 32); + Value* ea_lo64 = f.ZeroExtend(f.Truncate(ea, INT32_TYPE), INT64_TYPE); + Value* packed = f.Or(pc_hi64, ea_lo64); + for (size_t idx = 0; idx < vals.size(); ++idx) { + Value* cmp = f.CompareEQ(val32, f.LoadConstantUint32(vals[idx])); + f.StoreContext(offsetof(::xe::cpu::ppc::PPCContext, scratch), packed); + f.ContextBarrier(); + f.TrapTrue(cmp, static_cast(250 + idx)); + } +} + +// AUDIT-067 128-bit (vector) variant: checks each of the 4 32-bit lanes in a +// vector store. Used for stvx/stvxl/stvewx (memcpy-derived installs may use +// 128-bit vector stores). The matched lane is reflected in the dst by +// adding (lane * 4) so the handler can see exactly where in memory the +// value lands. Declared with external linkage so altivec.cc can call it. +void EmitAudit67ValueWatchVec(PPCHIRBuilder& f, uint32_t pc, + Value* vec128, Value* ea) { + const auto& vals = ::xe::cpu::audit67::vals(); + if (vals.empty()) return; + Value* pc_hi64 = f.LoadConstantUint64(static_cast(pc) << 32); + for (size_t idx = 0; idx < vals.size(); ++idx) { + Value* watch = f.LoadConstantUint32(vals[idx]); + for (uint8_t lane = 0; lane < 4; ++lane) { + Value* lane_val = f.Extract(vec128, lane, INT32_TYPE); + Value* cmp = f.CompareEQ(lane_val, watch); + Value* lane_off = f.LoadConstantUint32(static_cast(lane * 4)); + Value* dst32 = f.Add(f.Truncate(ea, INT32_TYPE), lane_off); + Value* packed = f.Or(pc_hi64, f.ZeroExtend(dst32, INT64_TYPE)); + f.StoreContext(offsetof(::xe::cpu::ppc::PPCContext, scratch), packed); + f.ContextBarrier(); + f.TrapTrue(cmp, static_cast(250 + idx)); + } + } +} + +// AUDIT-067 64-bit variant: same as above but checks BOTH halves of a 64-bit +// stored value. EA points at the start of the 8-byte store; the matched half +// is encoded into the trap idx via (250 + 2*idx + half), where half=0 means +// upper 32 bits (lower address), half=1 means lower 32 bits (upper address). +static void EmitAudit67ValueWatch64(PPCHIRBuilder& f, uint32_t pc, Value* val64, + Value* ea) { + const auto& vals = ::xe::cpu::audit67::vals(); + if (vals.empty()) return; + // PowerPC is big-endian: u64 stored at EA places upper-32 bits at EA+0 + // and lower-32 bits at EA+4. Check both halves against each watch value. + Value* upper32 = f.Truncate(f.Shr(val64, int8_t(32)), INT32_TYPE); // bits[63:32] + Value* lower32 = f.Truncate(val64, INT32_TYPE); // bits[31:0] + Value* pc_hi64 = f.LoadConstantUint64(static_cast(pc) << 32); + for (size_t idx = 0; idx < vals.size(); ++idx) { + // Upper half lands at EA+0. + { + Value* cmp = f.CompareEQ(upper32, f.LoadConstantUint32(vals[idx])); + Value* ea_lo64 = f.ZeroExtend(f.Truncate(ea, INT32_TYPE), INT64_TYPE); + Value* packed = f.Or(pc_hi64, ea_lo64); + f.StoreContext(offsetof(::xe::cpu::ppc::PPCContext, scratch), packed); + f.ContextBarrier(); + f.TrapTrue(cmp, static_cast(250 + idx)); + } + // Lower half lands at EA+4. + { + Value* cmp = f.CompareEQ(lower32, f.LoadConstantUint32(vals[idx])); + Value* ea_plus4 = + f.Add(f.Truncate(ea, INT32_TYPE), f.LoadConstantUint32(4)); + Value* ea_lo64 = f.ZeroExtend(ea_plus4, INT64_TYPE); + Value* packed = f.Or(pc_hi64, ea_lo64); + f.StoreContext(offsetof(::xe::cpu::ppc::PPCContext, scratch), packed); + f.ContextBarrier(); + f.TrapTrue(cmp, static_cast(250 + idx)); + } + } +} + // Integer load (A-13) int InstrEmit_lbz(PPCHIRBuilder& f, const InstrData& i) { @@ -518,9 +613,11 @@ int InstrEmit_stw(PPCHIRBuilder& f, const InstrData& i) { b = f.LoadGPR(i.D.RA); } Value* offset = f.LoadConstantInt64(XEEXTS16(i.D.DS)); - f.StoreOffset(b, offset, - f.ByteSwap(f.Truncate(f.LoadGPR(i.D.RT), INT32_TYPE))); - + Value* val32 = f.Truncate(f.LoadGPR(i.D.RT), INT32_TYPE); + f.StoreOffset(b, offset, f.ByteSwap(val32)); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch(f, i.address, val32, f.Add(b, offset)); + } return 0; } @@ -532,10 +629,14 @@ int InstrEmit_stmw(PPCHIRBuilder& f, const InstrData& i) { b = f.LoadGPR(i.D.RA); } + const bool watch_active = !::xe::cpu::audit67::vals().empty(); for (uint32_t j = 0; j < 32 - i.D.RT; ++j) { Value* offset = f.LoadConstantInt64(XEEXTS16(i.D.DS) + j * 4); - f.StoreOffset(b, offset, - f.ByteSwap(f.Truncate(f.LoadGPR(i.D.RT + j), INT32_TYPE))); + Value* val32 = f.Truncate(f.LoadGPR(i.D.RT + j), INT32_TYPE); + f.StoreOffset(b, offset, f.ByteSwap(val32)); + if (watch_active) { + EmitAudit67ValueWatch(f, i.address, val32, f.Add(b, offset)); + } } return 0; } @@ -545,8 +646,12 @@ int InstrEmit_stwu(PPCHIRBuilder& f, const InstrData& i) { // MEM(EA, 4) <- (RS)[32:63] // RA <- EA Value* ea = CalculateEA_i(f, i.D.RA, XEEXTS16(i.D.DS)); - f.Store(ea, f.ByteSwap(f.Truncate(f.LoadGPR(i.D.RT), INT32_TYPE))); + Value* val32 = f.Truncate(f.LoadGPR(i.D.RT), INT32_TYPE); + f.Store(ea, f.ByteSwap(val32)); StoreEA(f, i.D.RA, ea); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch(f, i.address, val32, ea); + } return 0; } @@ -555,8 +660,12 @@ int InstrEmit_stwux(PPCHIRBuilder& f, const InstrData& i) { // MEM(EA, 4) <- (RS)[32:63] // RA <- EA Value* ea = CalculateEA(f, i.X.RA, i.X.RB); - f.Store(ea, f.ByteSwap(f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE))); + Value* val32 = f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE); + f.Store(ea, f.ByteSwap(val32)); StoreEA(f, i.X.RA, ea); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch(f, i.address, val32, ea); + } return 0; } @@ -568,7 +677,11 @@ int InstrEmit_stwx(PPCHIRBuilder& f, const InstrData& i) { // EA <- b + (RB) // MEM(EA, 4) <- (RS)[32:63] Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB); - f.Store(ea, f.ByteSwap(f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE))); + Value* val32 = f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE); + f.Store(ea, f.ByteSwap(val32)); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch(f, i.address, val32, ea); + } return 0; } @@ -587,7 +700,11 @@ int InstrEmit_std(PPCHIRBuilder& f, const InstrData& i) { } Value* offset = f.LoadConstantInt64(XEEXTS16(i.DS.DS << 2)); - f.StoreOffset(b, offset, f.ByteSwap(f.LoadGPR(i.DS.RT))); + Value* val64 = f.LoadGPR(i.DS.RT); + f.StoreOffset(b, offset, f.ByteSwap(val64)); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch64(f, i.address, val64, f.Add(b, offset)); + } return 0; } @@ -596,8 +713,12 @@ int InstrEmit_stdu(PPCHIRBuilder& f, const InstrData& i) { // MEM(EA, 8) <- (RS) // RA <- EA Value* ea = CalculateEA_i(f, i.DS.RA, XEEXTS16(i.DS.DS << 2)); - f.Store(ea, f.ByteSwap(f.LoadGPR(i.DS.RT))); + Value* val64 = f.LoadGPR(i.DS.RT); + f.Store(ea, f.ByteSwap(val64)); StoreEA(f, i.DS.RA, ea); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch64(f, i.address, val64, ea); + } return 0; } @@ -606,8 +727,12 @@ int InstrEmit_stdux(PPCHIRBuilder& f, const InstrData& i) { // MEM(EA, 8) <- (RS) // RA <- EA Value* ea = CalculateEA(f, i.X.RA, i.X.RB); - f.Store(ea, f.ByteSwap(f.LoadGPR(i.X.RT))); + Value* val64 = f.LoadGPR(i.X.RT); + f.Store(ea, f.ByteSwap(val64)); StoreEA(f, i.X.RA, ea); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch64(f, i.address, val64, ea); + } return 0; } @@ -619,7 +744,11 @@ int InstrEmit_stdx(PPCHIRBuilder& f, const InstrData& i) { // EA <- b + (RB) // MEM(EA, 8) <- (RS) Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB); - f.Store(ea, f.ByteSwap(f.LoadGPR(i.X.RT))); + Value* val64 = f.LoadGPR(i.X.RT); + f.Store(ea, f.ByteSwap(val64)); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch64(f, i.address, val64, ea); + } return 0; } @@ -684,7 +813,11 @@ int InstrEmit_stwbrx(PPCHIRBuilder& f, const InstrData& i) { // EA <- b + (RB) // MEM(EA, 4) <- bswap((RS)[32:63]) Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB); - f.Store(ea, f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE)); + Value* val32 = f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE); + f.Store(ea, val32); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch(f, i.address, val32, ea); + } return 0; } @@ -696,7 +829,11 @@ int InstrEmit_stdbrx(PPCHIRBuilder& f, const InstrData& i) { // EA <- b + (RB) // MEM(EA, 8) <- bswap(RS) Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB); - f.Store(ea, f.LoadGPR(i.X.RT)); + Value* val64 = f.LoadGPR(i.X.RT); + f.Store(ea, val64); + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch64(f, i.address, val64, ea); + } return 0; } @@ -843,7 +980,8 @@ int InstrEmit_stdcx(PPCHIRBuilder& f, const InstrData& i) { // This will always succeed if under the global lock, however. Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB); - Value* rt = f.ByteSwap(f.LoadGPR(i.X.RT)); + Value* val64 = f.LoadGPR(i.X.RT); + Value* rt = f.ByteSwap(val64); if (cvars::no_reserved_ops) { f.Store(ea, rt); @@ -862,6 +1000,9 @@ int InstrEmit_stdcx(PPCHIRBuilder& f, const InstrData& i) { if (!cvars::no_reserved_ops) { f.MemoryBarrier(); } + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch64(f, i.address, val64, ea); + } return 0; } @@ -885,7 +1026,8 @@ int InstrEmit_stwcx(PPCHIRBuilder& f, const InstrData& i) { Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB); - Value* rt = f.ByteSwap(f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE)); + Value* val32 = f.Truncate(f.LoadGPR(i.X.RT), INT32_TYPE); + Value* rt = f.ByteSwap(val32); if (cvars::no_reserved_ops) { f.Store(ea, rt); @@ -904,7 +1046,9 @@ int InstrEmit_stwcx(PPCHIRBuilder& f, const InstrData& i) { if (!cvars::no_reserved_ops) { f.MemoryBarrier(); } - + if (!::xe::cpu::audit67::vals().empty()) { + EmitAudit67ValueWatch(f, i.address, val32, ea); + } return 0; } // Floating-point load (A-19) diff --git a/src/xenia/cpu/ppc/ppc_hir_builder.cc b/src/xenia/cpu/ppc/ppc_hir_builder.cc index 42d996cba..e2f7a45db 100644 --- a/src/xenia/cpu/ppc/ppc_hir_builder.cc +++ b/src/xenia/cpu/ppc/ppc_hir_builder.cc @@ -34,6 +34,97 @@ DEFINE_bool( "unimplemented PowerPC instruction is encountered.", "CPU"); +// AUDIT-061 — multi-PC branch probe. Parses cvars::audit_61_branch_probe_pcs +// once and exposes a (pc -> trap_id) lookup table. trap_id range [200, 65535]. +// PCs outside the table are not probed. Native side reads g_audit61_pcs[idx]. +#include +#include +namespace xe { +namespace cpu { +namespace audit61 { +constexpr uint16_t kTrapBase = 200; +constexpr size_t kMaxPcs = 32; +static std::vector g_pcs; +static bool g_parsed = false; + +const std::vector& pcs() { + if (!g_parsed) { + g_parsed = true; + const std::string& csv = cvars::audit_61_branch_probe_pcs; + size_t pos = 0; + while (pos < csv.size() && g_pcs.size() < kMaxPcs) { + size_t end = csv.find(',', pos); + std::string tok = csv.substr(pos, end - pos); + // strip whitespace + while (!tok.empty() && (tok.front() == ' ' || tok.front() == '\t')) + tok.erase(tok.begin()); + while (!tok.empty() && (tok.back() == ' ' || tok.back() == '\t')) + tok.pop_back(); + if (!tok.empty()) { + try { + uint32_t v = static_cast(std::stoul(tok, nullptr, 0)); + g_pcs.push_back(v); + } catch (...) { + } + } + if (end == std::string::npos) break; + pos = end + 1; + } + } + return g_pcs; +} + +// Returns trap id for pc, or 0 if pc not in probe set. +uint16_t trap_id_for(uint32_t pc) { + const auto& v = pcs(); + for (size_t i = 0; i < v.size(); ++i) { + if (v[i] == pc) return static_cast(kTrapBase + i); + } + return 0; +} +} // namespace audit61 + +// AUDIT-067 — value-watch. Parses cvars::audit_67_value_watch once, exposes +// values via vals(). Trap codes for matches start at kTrapBase = 250. +namespace audit67 { +constexpr uint16_t kTrapBase = 250; +constexpr size_t kMaxVals = 4; +static std::vector g_vals; +static bool g_parsed = false; + +const std::vector& vals() { + if (!g_parsed) { + g_parsed = true; + const std::string& csv = cvars::audit_67_value_watch; + size_t pos = 0; + while (pos < csv.size() && g_vals.size() < kMaxVals) { + size_t end = csv.find(',', pos); + std::string tok = csv.substr(pos, end - pos); + while (!tok.empty() && (tok.front() == ' ' || tok.front() == '\t')) + tok.erase(tok.begin()); + while (!tok.empty() && (tok.back() == ' ' || tok.back() == '\t')) + tok.pop_back(); + if (!tok.empty()) { + try { + uint32_t v = static_cast(std::stoul(tok, nullptr, 0)); + g_vals.push_back(v); + } catch (...) { + } + } + if (end == std::string::npos) break; + pos = end + 1; + } + XELOGI("AUDIT-067-INIT csv=\"{}\" parsed_count={}", csv, g_vals.size()); + for (size_t i = 0; i < g_vals.size(); ++i) { + XELOGI("AUDIT-067-INIT vals[{}] = 0x{:08X}", i, g_vals[i]); + } + } + return g_vals; +} +} // namespace audit67 +} // namespace cpu +} // namespace xe + namespace xe { namespace cpu { namespace ppc { @@ -174,6 +265,20 @@ bool PPCHIRBuilder::Emit(GuestFunction* function, uint32_t flags) { MaybeBreakOnInstruction(address); + // AUDIT-061: emit a trap before this instruction if it's on the probe + // list. The trap fires BEFORE the cmp/branch HIR emit so the native + // handler observes cr0/cr6 set by the *previous* instruction (the cmp + // that controls this conditional branch). ContextBarrier flushes + // HIR temporaries to PPCContext so the handler reads consistent state. + if (!::xe::cpu::audit61::pcs().empty()) { + uint16_t tid = ::xe::cpu::audit61::trap_id_for(address); + if (tid != 0) { + Comment("--audit_61_branch_probe target"); + ContextBarrier(); + Trap(tid); + } + } + InstrData i; i.address = address; i.code = code;