[Phase A] Expansive extraction tracing: call args, file.read, name-hash probe
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 1m36s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped

Extend the cvar-gated JSONL event_log tracer to extract Project Sylpheed
data/behaviour (reimplementation pivot). All additive, default-off, no
behaviour change (golden rule):

- Tier 1 (--phase_a_trace_args): fill the previously-empty kernel.call
  args (raw r3..r10 from PPCContext) + args_resolved.path, routed through
  the existing export bridge (shim trampoline body-only).
- Tier 2 (file.read): resolve NtReadFile handle->path via the object
  table (LookupObject<XFile>), emit {handle,path,offset,length,buffer_va}
  so runtime reads correlate to IPFB TOC entries.
- Tier 3 (--phase_a_hash_probe=<pc,...>): mirror audit_61 — a hashprobe
  HIR trap band (base 300, max 32 PCs) in ppc_hir_builder.cc + native
  handler TrapPhaseAHashProbe in x64_emitter.cc that derefs r3 as a
  bounded guest C-string and XELOGIs PHASE-A-HASHPROBE (log-only; cpu
  backend must not reach kernel event_log). Recovers the custom IPFB
  name-hash from observed (string -> hash) pairs.

New files event_log.{cc,h}; cpu_flags + ppc_hir_builder + x64_emitter
are whole-file snapshots (also carry the earlier uncommitted audit_*
probe plumbing they share). Cross-build and other instrumentation remain
uncommitted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-09 21:11:58 +02:00
parent 6de80dffe2
commit a154309022
6 changed files with 1384 additions and 0 deletions

View File

@@ -34,6 +34,141 @@ 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 <vector>
#include <string>
namespace xe {
namespace cpu {
namespace audit61 {
constexpr uint16_t kTrapBase = 200;
constexpr size_t kMaxPcs = 32;
static std::vector<uint32_t> g_pcs;
static bool g_parsed = false;
const std::vector<uint32_t>& 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<uint32_t>(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<uint16_t>(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<uint32_t> g_vals;
static bool g_parsed = false;
const std::vector<uint32_t>& 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<uint32_t>(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
// PHASE-A hash probe — multi-PC guest probe for recovering the IPFB/IDXD
// name-hash. Parses cvars::phase_a_hash_probe once; trap ids start at 300.
// At each fire the native handler derefs r3 as a guest C-string (the archive
// path being hashed) and logs it alongside r3..r6. Mirrors audit61.
namespace hashprobe {
constexpr uint16_t kTrapBase = 300;
constexpr size_t kMaxPcs = 32;
static std::vector<uint32_t> g_pcs;
static bool g_parsed = false;
const std::vector<uint32_t>& pcs() {
if (!g_parsed) {
g_parsed = true;
const std::string& csv = cvars::phase_a_hash_probe;
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);
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 {
g_pcs.push_back(static_cast<uint32_t>(std::stoul(tok, nullptr, 0)));
} catch (...) {
}
}
if (end == std::string::npos) break;
pos = end + 1;
}
}
return g_pcs;
}
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<uint16_t>(kTrapBase + i);
}
return 0;
}
} // namespace hashprobe
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace ppc {
@@ -174,6 +309,32 @@ 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);
}
}
// PHASE-A hash probe: trap before this instruction so the native handler
// observes r3 (arg string ptr) as set by the caller. ContextBarrier
// flushes HIR temporaries so the handler reads consistent GPRs.
if (!::xe::cpu::hashprobe::pcs().empty()) {
uint16_t tid = ::xe::cpu::hashprobe::trap_id_for(address);
if (tid != 0) {
Comment("--phase_a_hash_probe target");
ContextBarrier();
Trap(tid);
}
}
InstrData i;
i.address = address;
i.code = code;