diff --git a/src/xenia/cpu/function.cc b/src/xenia/cpu/function.cc index ebd8c5ba19e..296122aa6b4 100644 --- a/src/xenia/cpu/function.cc +++ b/src/xenia/cpu/function.cc @@ -44,6 +44,22 @@ bool BuiltinFunction::Call(ThreadState* thread_state, uint32_t return_address) { } assert_not_null(handler_); + + // Detect corrupted builtin argument pointers before calling the handler. + // A very low non-null address (< 0x10000) is almost certainly invalid and + // indicates memory corruption, likely from guest code buffer overflow. + // This check helps identify the problem before it causes a crash in the + // mutex operations within builtin handlers. + if (arg0_ && reinterpret_cast(arg0_) < 0x10000) { + XELOGE( + "BuiltinFunction '{}' detected corrupted arg0 pointer: {:p}. " + "This likely indicates memory corruption from guest code. " + "The emulation cannot continue safely.", + name(), arg0_); + assert_always("BuiltinFunction arg0 corrupted - guest code memory corruption detected"); + return false; + } + handler_(thread_state->context(), arg0_, arg1_); if (original_thread_state != thread_state) { @@ -129,8 +145,40 @@ bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) { ThreadState::Bind(thread_state); } + // Validate PPCContext critical pointers before executing guest code. + // This detects corruption that may have occurred from a previous function. + auto ctx = thread_state->context(); + auto& expected_global_mutex = xe::global_critical_region::mutex(); + if (ctx->global_mutex != &expected_global_mutex) { + uintptr_t corrupt_ptr = reinterpret_cast(ctx->global_mutex); + XELOGE( + "GuestFunction '{}' at 0x{:08X} called with corrupted PPCContext. " + "global_mutex pointer is {:p} / 0x{:X} (expected {:p}). " + "Corruption likely occurred in a previous function call.", + name(), address(), ctx->global_mutex, corrupt_ptr, + static_cast(&expected_global_mutex)); + assert_always( + "PPCContext already corrupted before function execution. Previous " + "guest function likely has buffer overflow."); + return false; + } + bool result = CallImpl(thread_state, return_address); + // Validate context after execution to catch corruption during this function. + if (ctx->global_mutex != &expected_global_mutex) { + uintptr_t corrupt_ptr = reinterpret_cast(ctx->global_mutex); + XELOGE( + "GuestFunction '{}' at 0x{:08X} CORRUPTED PPCContext during " + "execution. global_mutex changed to {:p} / 0x{:X}. " + "This function has a buffer overflow or invalid memory write.", + name(), address(), ctx->global_mutex, corrupt_ptr); + assert_always( + "Memory corruption detected in guest function execution. " + "The function has a buffer overflow bug."); + return false; + } + if (original_thread_state != thread_state) { ThreadState::Bind(original_thread_state); } @@ -139,4 +187,4 @@ bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) { } } // namespace cpu -} // namespace xe +} // namespace xe \ No newline at end of file diff --git a/src/xenia/cpu/processor.cc b/src/xenia/cpu/processor.cc index eb63a1abf0c..efcfd8f4e4d 100644 --- a/src/xenia/cpu/processor.cc +++ b/src/xenia/cpu/processor.cc @@ -337,6 +337,31 @@ bool Processor::Execute(ThreadState* thread_state, uint32_t address) { auto context = thread_state->context(); + // Validate critical PPCContext pointers before executing guest code. + // The global_mutex pointer is particularly susceptible to corruption from + // guest code writing beyond array bounds (e.g., VMX register array overflow). + // Detecting corruption here helps identify the source before crashes occur. + auto& expected_global_mutex = xe::global_critical_region::mutex(); + if (context->global_mutex != &expected_global_mutex) { + uintptr_t corrupt_ptr = reinterpret_cast(context->global_mutex); + XELOGE( + "PPCContext global_mutex pointer corrupted (expected {:p}, got {:p} / " + "0x{:X}). This indicates guest code is writing beyond allocated " + "boundaries. Common causes: VMX register overflow, stack corruption, or " + "invalid memory access in translated code. Thread ID: {}", + static_cast(&expected_global_mutex), context->global_mutex, + corrupt_ptr, thread_state->thread_id()); + + // Log additional context for debugging + XELOGE(" Function address: 0x{:08X}", address); + XELOGE(" Stack pointer (r1): 0x{:08X}", context->r[1]); + + assert_always( + "PPCContext corruption detected - cannot continue safely. Check for " + "guest code buffer overflows or emulator bugs in array bound checks."); + return false; + } + // Pad out stack a bit, as some games seem to overwrite the caller by about // 16 to 32b. context->r[1] -= 64 + 112; @@ -349,6 +374,23 @@ bool Processor::Execute(ThreadState* thread_state, uint32_t address) { // Execute the function. auto result = function->Call(thread_state, uint32_t(context->lr)); + // Validate context integrity after execution to detect corruption during + // the function call. This helps narrow down which guest functions cause + // memory corruption. + if (context->global_mutex != &expected_global_mutex) { + uintptr_t corrupt_ptr = reinterpret_cast(context->global_mutex); + XELOGE( + "PPCContext global_mutex corrupted DURING function execution at " + "0x{:08X}. Pointer changed from {:p} to {:p} / 0x{:X}. This " + "indicates the executed function wrote beyond its allocated memory.", + address, static_cast(&expected_global_mutex), + context->global_mutex, corrupt_ptr); + assert_always( + "Memory corruption detected during function execution. The executed " + "guest code has a buffer overflow or invalid memory write."); + return false; + } + context->lr = previous_lr; context->r[1] += 64 + 112; @@ -1302,4 +1344,4 @@ uint32_t Processor::CalculateNextGuestInstruction(ThreadDebugInfo* thread_info, } } // namespace cpu -} // namespace xe +} // namespace xe \ No newline at end of file