diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index 5da8f6adc..e54f1f3e0 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,47 @@ DEFINE_bool(instrument_call_times, false, "Compute time taken for functions, for profiling guest code", "x64"); #endif + +// AUDIT-061: forward decl of the PC table (defined in ppc_hir_builder.cc). +namespace xe { +namespace cpu { +namespace audit61 { +const std::vector& pcs(); +} // namespace audit61 +} // namespace cpu +} // namespace xe + +// AUDIT-061: handler for trap codes >= 200. 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; +} + namespace xe { namespace cpu { namespace backend { @@ -455,6 +498,13 @@ void X64Emitter::Trap(uint16_t trap_type) { // ? break; default: + // AUDIT-061: trap codes >= 200 dispatch the branch-probe handler. + // arg0 = idx into ::xe::cpu::audit61::pcs(). + if (trap_type >= 200) { + 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..2acce8db3 100644 --- a/src/xenia/cpu/cpu_flags.cc +++ b/src/xenia/cpu/cpu_flags.cc @@ -57,3 +57,16 @@ 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"); diff --git a/src/xenia/cpu/cpu_flags.h b/src/xenia/cpu/cpu_flags.h index 38c4f98ba..5731804f4 100644 --- a/src/xenia/cpu/cpu_flags.h +++ b/src/xenia/cpu/cpu_flags.h @@ -35,4 +35,11 @@ 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); + #endif // XENIA_CPU_CPU_FLAGS_H_ diff --git a/src/xenia/cpu/ppc/ppc_hir_builder.cc b/src/xenia/cpu/ppc/ppc_hir_builder.cc index 42d996cba..adc431fd2 100644 --- a/src/xenia/cpu/ppc/ppc_hir_builder.cc +++ b/src/xenia/cpu/ppc/ppc_hir_builder.cc @@ -34,6 +34,58 @@ 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 +} // namespace cpu +} // namespace xe + namespace xe { namespace cpu { namespace ppc { @@ -174,6 +226,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;