From 6069071e4d4a8fd7d1c9f39c7ca1d2e93e650ac6 Mon Sep 17 00:00:00 2001 From: Nekotekina Date: Mon, 5 Jan 2015 00:46:31 +0300 Subject: [PATCH] Code moved, attempt to make SIGSEGV handler --- Utilities/Thread.cpp | 371 +++++++++++++++++++++++++++++++++++- rpcs3/Emu/CPU/CPUThread.cpp | 245 ------------------------ 2 files changed, 370 insertions(+), 246 deletions(-) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 6014c607bf..14faf0903d 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -1,10 +1,15 @@ #include "stdafx.h" -#include "Emu/System.h" #include "Log.h" +#include "Emu/System.h" +#include "Emu/CPU/CPUThread.h" +#include "Emu/Syscalls/Syscalls.h" #include "Thread.h" #ifdef _WIN32 #include +#else +#include +#include #endif void SetCurrentThreadDebugName(const char* threadName) @@ -35,6 +40,350 @@ void SetCurrentThreadDebugName(const char* threadName) #endif } +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_IMM32, +}; + +enum x64_op_t : u32 +{ + X64OP_LOAD, // obtain and put the value into x64 register (from Memory.ReadMMIO32, for example) + X64OP_STORE, // take the value from x64 register or an immediate and use it (pass in Memory.WriteMMIO32, for example) + // example: add eax,[rax] -> X64OP_LOAD_ADD (add the value to x64 register) + // example: add [rax],eax -> X64OP_LOAD_ADD_STORE (this will probably never happen for MMIO registers) +}; + +void decode_x64_reg_op(const u8* code, x64_op_t& decoded_op, x64_reg_t& decoded_reg, size_t& decoded_size) +{ + // simple analysis of x64 code allows to reinterpret MOV or other instructions in any desired way + decoded_size = 0; + + u8 rex = 0; + u8 reg = 0; // set to 8 by REX prefix + u8 pg2 = 0; + + // check prefixes: + for (;; code++, decoded_size++) + { + switch (const u8 prefix = *code) + { + case 0xf0: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (LOCK prefix) found", code - decoded_size, prefix); // group 1 + case 0xf2: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REPNE/REPNZ prefix) found", code - decoded_size, prefix); // group 1 + case 0xf3: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REP/REPE/REPZ prefix) found", code - decoded_size, prefix); // group 1 + + case 0x2e: // group 2 + case 0x36: + case 0x3e: + case 0x26: + case 0x64: + case 0x65: + { + if (!pg2) + { + pg2 = prefix; // probably, segment register + continue; + } + else + { + throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (group 2 prefix) found after 0x%.2X", code - decoded_size, prefix, pg2); + } + } + + case 0x66: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (operand-size override prefix) found", code - decoded_size, prefix); // group 3 + case 0x67: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (address-size override prefix) found", code - decoded_size, prefix); // group 4 + + default: + { + if ((prefix & 0xf0) == 0x40) // check REX prefix + { + if (rex) + { + throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REX prefix) found after 0x%.2X", code - decoded_size, prefix, rex); + } + if (prefix & 0x80) // check REX.W bit + { + throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REX.W bit) found", code - decoded_size, prefix); + } + if (prefix & 0x04) // check REX.R bit + { + reg = 8; + } + rex = prefix; + continue; + } + } + } + + break; + } + + 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 (not tested) + { + 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 addr64 = (u64)pExp->ExceptionRecord->ExceptionInformation[1] - (u64)Memory.GetBaseAddr(); + const bool is_writing = pExp->ExceptionRecord->ExceptionInformation[0] != 0; + if (u == EXCEPTION_ACCESS_VIOLATION && addr64 < 0x100000000ull) + { + 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, nullptr); + + // it's dangerous because destructors won't be executed + } + // TODO: allow recovering from a page fault as a feature of PS3 virtual memory + if (CPUThread* t = GetCurrentCPUThread()) + { + 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 + { + throw fmt::Format("Access violation %s location 0x%x", is_writing ? "writing" : "reading", (u32)addr); + } + } + + // else some fatal error (should crash) +} + +#else + +static const auto reg_table[16] = +{ + REG_RAX, REG_RCX, REG_RDX, REG_RBX, REG_RSP, REG_RBP, REG_RSI, REG_RDI, + REG_R8, REG_R9, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15 +}; + +void signal_handler(int sig, siginfo_t* info, ucontext_t* ctx) +{ + const u64 addr64 = (u64)info->si_addr - (u64)Memory.GetBaseAddr(); + //const bool is_writing = false; // TODO: get it correctly + if (addr64 < 0x100000000ull) + { + 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*)ctx->uc_mcontext.gregs[REG_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)ctx->uc_mcontext.gregs[reg_table[reg - X64R32]]; + } + else if (reg == X64_IMM32) + { + // load the immediate value (assuming it's at the end of the instruction) + reg_value = *(u32*)(ctx->uc_mcontext.gregs[REG_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 + ctx->uc_mcontext.gregs[reg_table[reg - X64R32]] = (u32)reg_value; + } + else + { + assert(!"Invalid x64_reg_t value (saving)"); + } + } + + // skip decoded instruction + ctx->uc_mcontext.gregs[REG_RIP] += size; + + return; // now execution should proceed + //setcontext(ctx); + } + + // TODO: allow recovering from a page fault as a feature of PS3 virtual memory + if (CPUThread* t = GetCurrentCPUThread()) + { + throw fmt::Format("Access violation %s location 0x%x (is_alive=%d, last_syscall=0x%llx (%s))", /*is_writing ? "writing" : "reading"*/ "at", (u32)addr, + t->IsAlive() ? 1 : 0, t->m_last_syscall, SysCalls::GetHLEFuncName((u32)t->m_last_syscall).c_str()); + } + else + { + throw fmt::Format("Access violation %s location 0x%x", /*is_writing ? "writing" : "reading"*/ "at", (u32)addr); + } + } + + // else some fatal error (not sure what will happen) + //exit(EXIT_FAILURE); +} + +const int sigaction_result = []() -> int +{ + struct sigaction sa; + + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = signal_handler; + return sigaction(SIGSEGV, &sa, NULL); +}; + +#endif + thread_local NamedThreadBase* g_tls_this_thread = nullptr; std::atomic g_thread_count(0); @@ -119,6 +468,12 @@ void ThreadBase::Start() { SetCurrentThreadDebugName(GetThreadName().c_str()); +#ifdef _WIN32 + auto old_se_translator = _set_se_translator(_se_translator); +#else + if (sigaction_result == -1) assert(!"sigaction() failed"); +#endif + SetCurrentNamedThread(this); g_thread_count++; @@ -138,6 +493,10 @@ void ThreadBase::Start() m_alive = false; SetCurrentNamedThread(nullptr); g_thread_count--; + +#ifdef _WIN32 + _set_se_translator(old_se_translator); +#endif }); } @@ -208,6 +567,12 @@ void thread::start(std::function func) { SetCurrentThreadDebugName(name.c_str()); +#ifdef _WIN32 + auto old_se_translator = _set_se_translator(_se_translator); +#else + if (sigaction_result == -1) assert(!"sigaction() failed"); +#endif + NamedThreadBase info(name); SetCurrentNamedThread(&info); g_thread_count++; @@ -227,6 +592,10 @@ void thread::start(std::function func) SetCurrentNamedThread(nullptr); g_thread_count--; + +#ifdef _WIN32 + _set_se_translator(old_se_translator); +#endif }); } diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 7a5db79e79..a4bcf1b3ef 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -254,243 +254,6 @@ 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 rex = 0; - u8 reg = 0; // set to 8 by REX prefix - u8 pg2 = 0; - - // check prefixes: - for (;; code++, decoded_size++) - { - switch (const u8 prefix = *code) - { - case 0xf0: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (LOCK prefix) found", code - decoded_size, prefix); // group 1 - case 0xf2: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REPNE/REPNZ prefix) found", code - decoded_size, prefix); // group 1 - case 0xf3: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REP/REPE/REPZ prefix) found", code - decoded_size, prefix); // group 1 - - case 0x2e: // group 2 - case 0x36: - case 0x3e: - case 0x26: - case 0x64: - case 0x65: - { - if (!pg2) - { - pg2 = prefix; // probably, segment selector - continue; - } - else - { - throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (group 2 prefix) found after 0x%.2X", code - decoded_size, prefix, pg2); - } - } - - case 0x66: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (operand-size override prefix) found", code - decoded_size, prefix); // group 3 - case 0x67: throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (address-size override prefix) found", code - decoded_size, prefix); // group 4 - - default: - { - if ((prefix & 0xf0) == 0x40) // check REX prefix - { - if (rex) - { - throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REX prefix) found after 0x%.2X", code - decoded_size, prefix, rex); - } - if (prefix & 0x80) // check REX.W bit - { - throw fmt::Format("decode_x64_reg_op(%.16llXh): 0x%.2X (REX.W bit) found", code - decoded_size, prefix); - } - if (prefix & 0x04) // check REX.R bit - { - reg = 8; - } - rex = prefix; - continue; - } - } - } - - break; - } - - 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 addr64 = (u64)pExp->ExceptionRecord->ExceptionInformation[1] - (u64)Memory.GetBaseAddr(); - const bool is_writing = pExp->ExceptionRecord->ExceptionInformation[0] != 0; - if (u == EXCEPTION_ACCESS_VIOLATION && addr64 < 0x100000000) - { - 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 - if (CPUThread* t = GetCurrentCPUThread()) - { - 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 - { - throw fmt::Format("Access violation %s location 0x%x", is_writing ? "writing" : "reading", (u32)addr); - } - } - - // else some fatal error (should crash) -} -#else -// TODO: linux version -#endif - void CPUThread::Task() { if (Ini.HLELogging.GetValue()) LOG_NOTICE(GENERAL, "%s enter", CPUThread::GetFName().c_str()); @@ -508,10 +271,6 @@ void CPUThread::Task() std::vector trace; -#ifdef _WIN32 - auto old_se_translator = _set_se_translator(_se_translator); -#endif - try { while (true) @@ -560,10 +319,6 @@ void CPUThread::Task() Emu.Pause(); } -#ifdef _WIN32 - _set_se_translator(old_se_translator); -#endif - if (trace.size()) { LOG_NOTICE(GENERAL, "Trace begin (%d elements)", trace.size());