Files
Xenia-Canary/src/xenia/kernel/xevent.cc
MechaCat02 07f09f8fe0 [Audit] round 24: signal-path probes (XEvent::Set + IO + NtDuplicateObject)
Adds AUDIT-HLC probes for every path that signals an XEvent or duplicates
its handle, so the round-23 puzzle (waits completing without visible
NtSetEvent/KeSetEvent) can be cross-referenced by underlying X_KEVENT VA
and by primary handle.

  * NtCreateEvent_inner: always log kevent_va for handle/VA cross-ref
  * XEvent::Set: universal hook prints primary handle + kevent_va + LR;
    catches all paths into the event regardless of shim used
  * NtSetEvent: extended with PPC back-chain walk for caller's guest_lr
    (same idiom as the wait probe), so the signaler function is
    immediately identifiable
  * NtReadFile / NtReadFileScatter / NtWriteFile: log when signal_event
    path fires ev->Set() inline at IO completion (bypasses both
    NtSetEvent and KeSetEvent shims)
  * CompleteOverlappedEx (kernel_state.cc): log when overlapped event
    is signaled at completion
  * NtDuplicateObject: log src/dst handle pair (round-24 confirmed silph
    event is dup'd before signaling, explaining the handle-mismatch
    puzzle between NtSetEvent's `handle=` argument and XEvent::Set's
    primary handle())

Result: silph wait at sub_821CB030+0x1B0 on handle F80000A0 is signaled
via NtSetEvent on handle F80000A8 (the duplicate created by sub_8245D9D8
at lr=0x82450DF4 inside the worker chain sub_82450A28 ← 0xA68 ← 0xB68).
Verdict A confirmed: signaler exists and is reachable, but round-17.β
missed it because the search keyed on the wrong handle. No bypass path;
the worker chain is identical between canary and ours-impl, but ours
signals different handles (work-item queue divergence at sub_82452DC0).

Audit-only diagnostic. No semantic changes. Net delta +86 LOC across 7
files. All probes gated on the existing audit_handle_lifecycle cvar
(default off). Tested via Sylpheed boot (35 s, 4 MB log). Audit-handle-
lifecycle-probes branch, local-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-12 15:43:23 +02:00

144 lines
3.9 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xevent.h"
#include "xenia/base/byte_stream.h"
#include "xenia/base/logging.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/kernel/kernel_flags.h"
namespace xe {
namespace kernel {
XEvent::XEvent(KernelState* kernel_state)
: XObject(kernel_state, kObjectType) {}
XEvent::~XEvent() = default;
void XEvent::Initialize(bool manual_reset, bool initial_state) {
assert_false(event_);
this->CreateNative<X_KEVENT>();
if (manual_reset) {
event_ = xe::threading::Event::CreateManualResetEvent(initial_state);
} else {
event_ = xe::threading::Event::CreateAutoResetEvent(initial_state);
}
assert_not_null(event_);
}
void XEvent::InitializeNative(void* native_ptr, X_DISPATCH_HEADER* header) {
assert_false(event_);
switch (header->type) {
case 0x00: // EventNotificationObject (manual reset)
manual_reset_ = true;
break;
case 0x01: // EventSynchronizationObject (auto reset)
manual_reset_ = false;
break;
default:
assert_always();
return;
}
bool initial_state = header->signal_state ? true : false;
if (manual_reset_) {
event_ = xe::threading::Event::CreateManualResetEvent(initial_state);
} else {
event_ = xe::threading::Event::CreateAutoResetEvent(initial_state);
}
assert_not_null(event_);
}
int32_t XEvent::Set(uint32_t priority_increment, bool wait) {
if (cvars::audit_handle_lifecycle) {
auto* ts = cpu::ThreadState::Get();
uint32_t lr = ts ? static_cast<uint32_t>(ts->context()->lr) : 0u;
XELOGKERNEL(
"AUDIT-HLC XEvent::Set handle={:08X} kevent_va={:08X} prio={} lr={:08X}",
handle(), guest_object(), priority_increment, lr);
}
set_priority_increment(priority_increment);
event_->Set();
return 1;
}
int32_t XEvent::Pulse(uint32_t priority_increment, bool wait) {
set_priority_increment(priority_increment);
event_->Pulse();
return 1;
}
int32_t XEvent::Reset() {
event_->Reset();
return 1;
}
void XEvent::Query(uint32_t* out_type, uint32_t* out_state) {
auto [type, state] = event_->Query();
*out_type = type;
*out_state = state;
}
void XEvent::Clear() { event_->Reset(); }
bool XEvent::Save(ByteStream* stream) {
XELOGD("XEvent {:08X} ({})", handle(), manual_reset_ ? "manual" : "auto");
SaveObject(stream);
bool signaled = true;
auto result =
xe::threading::Wait(event_.get(), false, std::chrono::milliseconds(0));
if (result == xe::threading::WaitResult::kSuccess) {
signaled = true;
} else if (result == xe::threading::WaitResult::kTimeout) {
signaled = false;
} else {
assert_always();
}
if (signaled) {
// Reset the event in-case it's an auto-reset.
event_->Set();
}
stream->Write<bool>(signaled);
stream->Write<bool>(manual_reset_);
return true;
}
object_ref<XEvent> XEvent::Restore(KernelState* kernel_state,
ByteStream* stream) {
auto evt = new XEvent(nullptr);
evt->kernel_state_ = kernel_state;
evt->RestoreObject(stream);
bool signaled = stream->Read<bool>();
evt->manual_reset_ = stream->Read<bool>();
if (evt->manual_reset_) {
evt->event_ = xe::threading::Event::CreateManualResetEvent(false);
} else {
evt->event_ = xe::threading::Event::CreateAutoResetEvent(false);
}
assert_not_null(evt->event_);
if (signaled) {
evt->event_->Set();
}
return object_ref<XEvent>(evt);
}
} // namespace kernel
} // namespace xe