From 1e5648c9a5b1d3ebd37c1a4000a3e87fbb13b902 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 6 Jun 2026 18:17:15 +0200 Subject: [PATCH] [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) --- .../kernel/xboxkrnl/xboxkrnl_threading.cc | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index 495ebca24..93a07272a 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -549,8 +549,9 @@ uint32_t xeKeSetEvent(X_KEVENT* event_ptr, uint32_t increment, uint32_t wait) { dword_result_t KeSetEvent_entry(pointer_t 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(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(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* 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(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(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(ctx->r[1]); + uint32_t back1 = xe::load_and_swap(ctx->TranslateVirtual(sp)); + if (back1) { + uint32_t back2 = + xe::load_and_swap(ctx->TranslateVirtual(back1)); + if (back2) { + guest_lr = xe::load_and_swap( + 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(*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; }