/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Phase A event-log emitter. Cvar-gated (default off). Schema v1. * Companion: xenia-rs/audit-runs/phase-a-diff-harness/schema-v1.md ****************************************************************************** */ #ifndef XENIA_KERNEL_EVENT_LOG_H_ #define XENIA_KERNEL_EVENT_LOG_H_ #include namespace xe { namespace kernel { namespace phase_a { // Object-type codes (must match ours's enum exactly — see schema-v1.md). enum ObjectType : uint32_t { kObjUnknown = 0x00, kObjEvent = 0x01, kObjMutant = 0x02, kObjSemaphore = 0x03, kObjTimer = 0x04, kObjThread = 0x05, kObjFile = 0x06, kObjIoCompletion = 0x07, kObjModule = 0x08, kObjEnumState = 0x09, kObjSection = 0x0A, kObjNotification = 0x0B, // Phase D Stage 1: pseudo-type used as the `object_type` input to // `ComputeSharedGlobalSemanticId` for RTL_CRITICAL_SECTION pointers. CS // is not a regular `XObject` (it lives as a guest-memory struct, not a // handle-tabled kernel object), but the `site_sid` field of // `contention.observed` reuses the shared-global SID recipe so the // Stage-3 manifest loader can compute the same SID in both engines. kObjCriticalSection = 0x0C, }; // Fast bool check (default off). Inlinable so we can guard hot paths cheaply. bool IsEnabled(); // Emitted once at startup if enabled (first line of the JSONL). void EmitSchemaHeader(); // FNV-1a 64-bit identity (see schema-v1.md). Both engines compute identically. uint64_t ComputeSemanticId(uint32_t create_site_pc, uint32_t creating_tid, uint64_t tid_event_idx_at_creation, uint32_t object_type); // One emit per imported kernel function invocation. Emitted by the export // trampoline before the kernel.call event. void EmitImportCall(const char* module_name, uint16_t ordinal, const char* fn_name); // Kernel call entry / return. args/args_resolved are deferred to a later // phase; v1 emits the name + return value only (sufficient for the diff // tool to align by sequence). void EmitKernelCall(const char* name); // Phase C+10: schema-v1 extension — emit kernel.call whose // `args_resolved` field carries a resolved `"path"` value (see // schema-v1.md kernel.call payload). `path == nullptr || *path == '\0'` // degrades to the existing empty-args_resolved form. void EmitKernelCallWithPath(const char* name, const char* path); void EmitKernelReturn(const char* name, uint64_t return_value); // Handle lifecycle. raw_handle_id is engine-local; the diff key is the // FNV-1a semantic id. void EmitHandleCreate(uint64_t semantic_id, uint32_t object_type, uint32_t raw_handle_id, const char* object_name); void EmitHandleDestroy(uint64_t semantic_id, uint32_t raw_handle_id, uint32_t prior_refcount); // Thread create/exit. parent_tid is the caller; entry_pc is the spawned // thread's first instruction. void EmitThreadCreate(uint64_t semantic_id, uint32_t parent_tid, uint32_t entry_pc, uint32_t ctx_ptr, uint32_t priority, uint32_t affinity, uint32_t stack_size, bool suspended); void EmitThreadExit(uint32_t exit_code); // Wait begin/end. handles_count + handles_semantic_ids array. void EmitWaitBegin(const uint64_t* handles_semantic_ids, uint32_t count, int64_t timeout_ns, bool alertable, bool wait_all); void EmitWaitEnd(uint32_t status, uint64_t woken_by_semantic_id_or_zero); // Returns the next per-tid event index (post-increment). Useful for // `tid_event_idx_at_creation` capture before calling ComputeSemanticId. uint64_t PeekTidEventIdx(); // Phase C+15-α: handle-semantic-ID registry. Maps raw handle id -> // FNV-1a 64-bit SID assigned at handle creation, so subsequent // `handle.destroy` / `wait.begin` / etc. events can emit a stable // cross-engine identity. No-op when event_log is disabled. void RegisterHandleSemanticId(uint32_t raw_handle_id, uint64_t sid); uint64_t LookupHandleSemanticId(uint32_t raw_handle_id); uint64_t ForgetHandleSemanticId(uint32_t raw_handle_id); // Convenience: at handle creation time, peek tid_event_idx, compute // the FNV-1a SID, register it for `raw_handle_id`, and emit a // `handle.create` event. Returns the SID. `create_site_pc` is set to // 0 by callers that cannot resolve a guest LR — both engines agree // on `0` for canonicalization at v1.1. uint64_t EmitHandleCreateAuto(uint32_t create_site_pc, uint32_t object_type, uint32_t raw_handle_id, const char* object_name); // Convenience: forget mapping + emit `handle.destroy` with the // previously registered SID. void EmitHandleDestroyAuto(uint32_t raw_handle_id, uint32_t prior_refcount); // Phase C+18: marker constant used as `create_site_pc` in shared-global // SID computation. Both engines must use exactly this value. See // schema-v1.md §"Shared-global SIDs". constexpr uint32_t kSharedGlobalSidMarker = 0xC01AB005; // Phase C+18: compute the scheduling-invariant SID for a // **process-global** kernel dispatcher (canary's // `XObject::GetNativeObject` lazy-wrap on first guest-thread touch). // Keyed on `(SHARED_GLOBAL_SID_MARKER, 0, pointer, object_type)` so the // SID depends only on the object's identity, not on the scheduling order. uint64_t ComputeSharedGlobalSemanticId(uint32_t pointer, uint32_t object_type); // Phase C+18: emit `handle.create` with a scheduling-invariant SID for // a process-global kernel dispatcher synthesized by // `XObject::GetNativeObject`. The SID is computed via // `ComputeSharedGlobalSemanticId(pointer, object_type)` so the same // dispatcher yields the same SID in both engines regardless of which // guest thread happens to be the first toucher. `raw_handle_id` is the // XObject's allocated handle; `pointer` is the guest dispatcher // (`X_DISPATCH_HEADER*`) pointer (used only to derive the SID). // // Caller MUST avoid the regular AddHandle emit for this XObject (set // the in-flight thread-local marker around the `new XEvent` / // `new XSemaphore` ctor and have the AddHandle hook short-circuit). void EmitHandleCreateSharedGlobal(uint32_t pointer, uint32_t object_type, uint32_t raw_handle_id, const char* object_name); // Phase C+18: thread-local flag indicating the current host thread is // inside `XObject::GetNativeObject` lazy-wrap, which constructs a new // XEvent/XSemaphore wrapper for a process-global guest dispatcher. // `AddHandle` consults this to skip its regular per-thread // `handle.create` emit; the explicit shared-global emit happens in // `GetNativeObject` after `StashHandle`. The flag is a single // host-thread-local bool — re-entrancy is not expected (the global // critical region is held throughout). void SetInGetNativeObject(bool active); bool IsInGetNativeObject(); // Phase D Stage 1: emit a `contention.observed` event from // `RtlEnterCriticalSection_entry` when the spin-loop is exhausted and // control falls through to `xeKeWaitForSingleObject`. The Stage-2 manifest // builder filters on `contended=true` and keys on `(tid, tid_event_idx)` // so the Stage-3 replay loop in ours can park its own `rtl_enter_critical_section` // at exactly the same per-tid ordinal. // // `cs_guest_ptr` is the guest VA of the `X_RTL_CRITICAL_SECTION` (the // argument to RtlEnterCS). `site_sid` is computed via // `ComputeSharedGlobalSemanticId(cs_guest_ptr, kObjCriticalSection)` so // both engines agree on the SID for the same CS pointer. // // Gated on `cvars::kernel_emit_contention && IsEnabled()`. Default // behavior (cvar off) is byte-identical to pre-Stage-1 canary. void EmitContentionObserved(uint32_t cs_guest_ptr, bool contended); // ── Expansive extraction tracing (game-data RE; gated by phase_a_trace_args / // phase_a_hash_probe, default off) ───────────────────────────────────────── // kernel.call with populated `args` (raw r3..r10 from the PPCContext, passed // as void*) and `args_resolved` (the resolved file path when known). void EmitKernelCallArgs(const char* name, void* ppc_context, const char* resolved_path); // file.read — a guest read from an open file handle (path resolved from the // handle via the object table). Enables correlating .pak reads to TOC entries. void EmitFileRead(uint32_t handle, const char* path, uint64_t offset, uint32_t length, uint32_t buffer_va); // guest.call — emitted by the guest-PC hash probe: `arg_str` is r3 dereferenced // as a guest C-string (the archive path being hashed). Used to recover the // custom IPFB/IDXD name-hash by observing (string -> hash) pairs. void EmitGuestCall(uint32_t pc, const char* arg_str, uint32_t r3, uint32_t r4, uint32_t r5, uint32_t r6); } // namespace phase_a } // namespace kernel } // namespace xe #endif // XENIA_KERNEL_EVENT_LOG_H_