diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index b3986e970d..99c3d18865 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -254,22 +254,195 @@ void CPUThread::ExecOnce() SendDbgCommand(DID_PAUSED_THREAD, this); } +enum x64_reg_t : u32 +{ + X64R_EAX, + X64R_ECX, + X64R_EDX, + X64R_EBX, + X64R_ESP, + X64R_EBP, + X64R_ESI, + X64R_EDI, + X64R_R8D, + X64R_R9D, + X64R_R10D, + X64R_R11D, + X64R_R12D, + X64R_R13D, + X64R_R14D, + X64R_R15D, + X64R32 = X64R_EAX, + + X64_IMM8, + X64_IMM16, + X64_IMM32, + X64_IMM64, +}; + +enum x64_op_t : u32 +{ + X64OP_LOAD, + X64OP_STORE, +}; + +void decode_x64_reg_op(const u8* code, x64_op_t& decoded_op, x64_reg_t& decoded_reg, size_t& decoded_size) +{ + decoded_size = 0; + + u8 reg = 0; + if ((*code & 0xf0) == 0x40) // check REX prefix + { + if (*code & 0x80) // check REX.W bit + { + throw fmt::Format("decode_x64_reg_op(%.16llXh): REX.W bit found", code - decoded_size); + } + if (*code & 0x04) // check REX.R bit + { + reg = 8; + } + code++; + decoded_size++; + } + + if (*code == 0x66) + { + throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x66 prefix found", code - decoded_size); + code++; + decoded_size++; + } + + auto get_modRM_r32 = [](const u8* code, const u8 reg_base) -> x64_reg_t + { + return (x64_reg_t)((((*code & 0x38) >> 3) | reg_base) + X64R32); + }; + + auto get_modRM_size = [](const u8* code) -> size_t + { + switch (*code >> 6) // check Mod + { + case 0: return (*code & 0x07) == 4 ? 2 : 1; // check SIB + case 1: return (*code & 0x07) == 4 ? 3 : 2; // check SIB (disp8) + case 2: return (*code & 0x07) == 4 ? 6 : 5; // check SIB (disp32) + default: return 1; + } + }; + + decoded_size++; + switch (const u8 op1 = *code++) + { + case 0x89: // MOV r/m32, r32 + { + decoded_op = X64OP_STORE; + decoded_reg = get_modRM_r32(code, reg); + decoded_size += get_modRM_size(code); + return; + } + case 0x8b: // MOV r32, r/m32 + { + decoded_op = X64OP_LOAD; + decoded_reg = get_modRM_r32(code, reg); + decoded_size += get_modRM_size(code); + return; + } + case 0xc7: + { + if (get_modRM_r32(code, 0) == X64R_EAX) // MOV r/m32, imm32 + { + decoded_op = X64OP_STORE; + decoded_reg = X64_IMM32; + decoded_size = get_modRM_size(code) + 4; + return; + } + } + default: + { + throw fmt::Format("decode_x64_reg_op(%.16llX): unsupported opcode found (0x%.2X, 0x%.2X, 0x%.2X)", code - decoded_size, op1, code[0], code[1]); + } + } +} + #ifdef _WIN32 void _se_translator(unsigned int u, EXCEPTION_POINTERS* pExp) { - const u64 addr = (u64)pExp->ExceptionRecord->ExceptionInformation[1] - (u64)Memory.GetBaseAddr(); + const u64 addr64 = (u64)pExp->ExceptionRecord->ExceptionInformation[1] - (u64)Memory.GetBaseAddr(); + const bool is_writing = pExp->ExceptionRecord->ExceptionInformation[0] != 0; CPUThread* t = GetCurrentCPUThread(); - if (u == EXCEPTION_ACCESS_VIOLATION && addr < 0x100000000 && t) + if (u == EXCEPTION_ACCESS_VIOLATION && addr64 < 0x100000000 && t) { - // TODO: allow recovering from a page fault - throw fmt::Format("Access violation: addr = 0x%x (is_alive=%d, last_syscall=0x%llx (%s))", - (u32)addr, t->IsAlive() ? 1 : 0, t->m_last_syscall, SysCalls::GetHLEFuncName((u32)t->m_last_syscall).c_str()); - } - else - { - // some fatal error (should crash) - return; + const u32 addr = (u32)addr64; + if (addr >= RAW_SPU_BASE_ADDR && (addr % RAW_SPU_OFFSET) >= RAW_SPU_PROB_OFFSET) // RawSPU MMIO registers + { + // one x64 instruction is manually decoded and interpreted + x64_op_t op; + x64_reg_t reg; + size_t size; + decode_x64_reg_op((const u8*)pExp->ContextRecord->Rip, op, reg, size); + + // get x64 reg value (for store operations) + u64 reg_value; + if (reg - X64R32 < 16) + { + // load the value from x64 register + reg_value = (u32)(&pExp->ContextRecord->Rax)[reg - X64R32]; + } + else if (reg == X64_IMM32) + { + // load the immediate value (assuming it's at the end of the instruction) + reg_value = *(u32*)(pExp->ContextRecord->Rip + size - 4); + } + else + { + assert(!"Invalid x64_reg_t value"); + } + + bool save_reg = false; + + switch (op) + { + case X64OP_LOAD: + { + assert(!is_writing); + reg_value = re32(Memory.ReadMMIO32(addr)); + save_reg = true; + break; + } + case X64OP_STORE: + { + assert(is_writing); + Memory.WriteMMIO32(addr, re32((u32)reg_value)); + break; + } + default: assert(!"Invalid x64_op_t value"); + } + + // save x64 reg value (for load operations) + if (save_reg) + { + if (reg - X64R32 < 16) + { + // store the value into x64 register + (&pExp->ContextRecord->Rax)[reg - X64R32] = (u32)reg_value; + } + else + { + assert(!"Invalid x64_reg_t value (saving)"); + } + } + + // skip decoded instruction + pExp->ContextRecord->Rip += size; + // restore context (further code shouldn't be reached) + RtlRestoreContext(pExp->ContextRecord, pExp->ExceptionRecord); + + // it's dangerous because destructors won't be executed + } + // TODO: allow recovering from a page fault as a feature of PS3 virtual memory + throw fmt::Format("Access violation %s location 0x%x (is_alive=%d, last_syscall=0x%llx (%s))", + is_writing ? "writing" : "reading", (u32)addr, t->IsAlive() ? 1 : 0, t->m_last_syscall, SysCalls::GetHLEFuncName((u32)t->m_last_syscall).c_str()); } + + // else some fatal error (should crash) } #else // TODO: linux version diff --git a/rpcs3/Emu/Memory/Memory.cpp b/rpcs3/Emu/Memory/Memory.cpp index 9aa328b14e..6c5d405eab 100644 --- a/rpcs3/Emu/Memory/Memory.cpp +++ b/rpcs3/Emu/Memory/Memory.cpp @@ -170,28 +170,29 @@ void MemoryBase::Close() MemoryBlocks.clear(); } -bool MemoryBase::WriteMMIO32(u32 addr, const u32 data) +void MemoryBase::WriteMMIO32(u32 addr, const u32 data) { LV2_LOCK(0); if (RawSPUMem[(addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET] && ((RawSPUThread*)RawSPUMem[(addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET])->Write32(addr, data)) { - return true; + return; } - return false; + throw fmt::Format("%s(addr=0x%x, data=0x%x) failed", __FUNCTION__, addr, data); } -bool MemoryBase::ReadMMIO32(u32 addr, u32& res) +u32 MemoryBase::ReadMMIO32(u32 addr) { LV2_LOCK(0); + u32 res; if (RawSPUMem[(addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET] && ((RawSPUThread*)RawSPUMem[(addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET])->Read32(addr, &res)) { - return true; + return res; } - return false; + throw fmt::Format("%s(addr=0x%x) failed", __FUNCTION__, addr); } bool MemoryBase::Map(const u64 addr, const u32 size) diff --git a/rpcs3/Emu/Memory/Memory.h b/rpcs3/Emu/Memory/Memory.h index 069e7a8609..1fccab32b8 100644 --- a/rpcs3/Emu/Memory/Memory.h +++ b/rpcs3/Emu/Memory/Memory.h @@ -127,9 +127,9 @@ public: void Close(); - __noinline bool WriteMMIO32(u32 addr, const u32 data); + __noinline void WriteMMIO32(u32 addr, const u32 data); - __noinline bool ReadMMIO32(u32 addr, u32& res); + __noinline u32 ReadMMIO32(u32 addr); u32 GetUserMemTotalSize() { diff --git a/rpcs3/Emu/Memory/vm.h b/rpcs3/Emu/Memory/vm.h index ed78cf54c2..1e6b64959c 100644 --- a/rpcs3/Emu/Memory/vm.h +++ b/rpcs3/Emu/Memory/vm.h @@ -64,20 +64,12 @@ namespace vm static u32 read32(u32 addr) { - u32 res; - if (addr < RAW_SPU_BASE_ADDR || (addr % RAW_SPU_OFFSET) < RAW_SPU_PROB_OFFSET || !Memory.ReadMMIO32((u32)addr, res)) - { - res = re32(*(u32*)((u8*)g_base_addr + addr)); - } - return res; + return re32(*(u32*)((u8*)g_base_addr + addr));; } static void write32(u32 addr, be_t value) { - if (addr < RAW_SPU_BASE_ADDR || (addr % RAW_SPU_OFFSET) < RAW_SPU_PROB_OFFSET || !Memory.WriteMMIO32((u32)addr, value)) - { - *(be_t*)((u8*)g_base_addr + addr) = value; - } + *(be_t*)((u8*)g_base_addr + addr) = value; } static u64 read64(u32 addr)