Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion src/xenia/cpu/function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<uintptr_t>(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) {
Expand Down Expand Up @@ -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<uintptr_t>(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<void*>(&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<uintptr_t>(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);
}
Expand All @@ -139,4 +187,4 @@ bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) {
}

} // namespace cpu
} // namespace xe
} // namespace xe
44 changes: 43 additions & 1 deletion src/xenia/cpu/processor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<uintptr_t>(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<void*>(&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;
Expand All @@ -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<uintptr_t>(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<void*>(&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;

Expand Down Expand Up @@ -1302,4 +1344,4 @@ uint32_t Processor::CalculateNextGuestInstruction(ThreadDebugInfo* thread_info,
}

} // namespace cpu
} // namespace xe
} // namespace xe