[Audit] handle-lifecycle: include guest LR + one-frame stack-walk on waits

Extend the AUDIT-HLC probes with guest LR capture:
  - NtCreateEvent, NtSetEvent, KeSetEvent: log immediate guest LR
    (cpu::ThreadState::Get()->context()->lr) so callers can be
    attributed to specific guest functions.
  - NtWaitForSingleObjectEx entry + _done: also walk one PPC stack
    frame up to recover the guest_lr of the wait wrapper's caller
    (Xbox 360 EABI: saved LR at [prev_sp - 8], see xenia-rs's
    walk_guest_back_chain). lr_enter alone gives only the kernel
    wait wrapper return address (e.g. 0x824AC578); guest_lr surfaces
    the actual call site.

Still cvar-gated on audit_handle_lifecycle. Used in audit-059 round 4
to map canary handles F8000xxx to ours' 0x12xx wedges via guest LR
matching against silph::UImpl/GamePart cluster.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 18:17:15 +02:00
parent d031d7c513
commit 1e5648c9a5

View File

@@ -549,8 +549,9 @@ uint32_t xeKeSetEvent(X_KEVENT* event_ptr, uint32_t increment, uint32_t wait) {
dword_result_t KeSetEvent_entry(pointer_t<X_KEVENT> event_ptr,
dword_t increment, dword_t wait) {
if (cvars::audit_handle_lifecycle) {
XELOGKERNEL("AUDIT-HLC KeSetEvent guest_ptr={:08X}",
uint32_t(event_ptr.guest_address()));
uint32_t lr = static_cast<uint32_t>(cpu::ThreadState::Get()->context()->lr);
XELOGKERNEL("AUDIT-HLC KeSetEvent guest_ptr={:08X} lr={:08X}",
uint32_t(event_ptr.guest_address()), lr);
}
return xeKeSetEvent(event_ptr, increment, wait);
}
@@ -610,9 +611,12 @@ dword_result_t NtCreateEvent_entry(
*handle_ptr = ev->handle();
}
if (cvars::audit_handle_lifecycle) {
XELOGKERNEL("AUDIT-HLC NtCreateEvent handle={:08X} type={} initial_state={}",
handle_ptr ? uint32_t(*handle_ptr) : 0u,
uint32_t(event_type), uint32_t(initial_state));
uint32_t lr = static_cast<uint32_t>(cpu::ThreadState::Get()->context()->lr);
XELOGKERNEL(
"AUDIT-HLC NtCreateEvent handle={:08X} type={} initial_state={} "
"lr={:08X}",
handle_ptr ? uint32_t(*handle_ptr) : 0u, uint32_t(event_type),
uint32_t(initial_state), lr);
}
return X_STATUS_SUCCESS;
}
@@ -640,7 +644,9 @@ uint32_t xeNtSetEvent(uint32_t handle, xe::be<uint32_t>* previous_state_ptr) {
dword_result_t NtSetEvent_entry(dword_t handle, lpdword_t previous_state_ptr) {
if (cvars::audit_handle_lifecycle) {
XELOGKERNEL("AUDIT-HLC NtSetEvent handle={:08X}", uint32_t(handle));
uint32_t lr = static_cast<uint32_t>(cpu::ThreadState::Get()->context()->lr);
XELOGKERNEL("AUDIT-HLC NtSetEvent handle={:08X} lr={:08X}",
uint32_t(handle), lr);
}
return xeNtSetEvent(handle, previous_state_ptr);
}
@@ -1016,17 +1022,41 @@ dword_result_t NtWaitForSingleObjectEx_entry(dword_t object_handle,
dword_t wait_mode,
dword_t alertable,
lpqword_t timeout_ptr) {
uint32_t lr_enter = 0;
uint32_t guest_lr = 0;
if (cvars::audit_handle_lifecycle) {
XELOGKERNEL("AUDIT-HLC NtWaitForSingleObjectEx handle={:08X} alertable={}",
uint32_t(object_handle), uint32_t(alertable));
auto* ctx = cpu::ThreadState::Get()->context();
lr_enter = static_cast<uint32_t>(ctx->lr);
// Walk one PPC stack frame up from the wait wrapper's frame to recover
// the GUEST caller's LR (lr_enter is just the kernel-internal wait
// wrapper's return address; we want the function that called *it*).
// Xbox 360 PPC convention: prologue stores caller's LR at [old_sp - 8]
// *before* bumping r1 to the new frame, so from any frame, the LR saved
// by that frame's prologue lives at (back_chain - 8). See xenia-rs
// walk_guest_back_chain in crates/xenia-kernel/src/state.rs.
uint32_t sp = static_cast<uint32_t>(ctx->r[1]);
uint32_t back1 = xe::load_and_swap<uint32_t>(ctx->TranslateVirtual(sp));
if (back1) {
uint32_t back2 =
xe::load_and_swap<uint32_t>(ctx->TranslateVirtual(back1));
if (back2) {
guest_lr = xe::load_and_swap<uint32_t>(
ctx->TranslateVirtual(back2 - 8));
}
}
XELOGKERNEL(
"AUDIT-HLC NtWaitForSingleObjectEx handle={:08X} alertable={} "
"lr={:08X} guest_lr={:08X}",
uint32_t(object_handle), uint32_t(alertable), lr_enter, guest_lr);
}
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
uint32_t result = NtWaitForSingleObjectEx(object_handle, wait_mode, alertable,
timeout_ptr ? &timeout : nullptr);
if (cvars::audit_handle_lifecycle) {
XELOGKERNEL(
"AUDIT-HLC NtWaitForSingleObjectEx_done handle={:08X} result={:08X}",
uint32_t(object_handle), result);
"AUDIT-HLC NtWaitForSingleObjectEx_done handle={:08X} result={:08X} "
"lr={:08X} guest_lr={:08X}",
uint32_t(object_handle), result, lr_enter, guest_lr);
}
return result;
}