#include "EeExecutor.h" #include "../Ps2Const.h" #include "AlignedAlloc.h" #include #if defined(__unix__) || defined(__ANDROID__) || defined(__APPLE__) #include #endif #if defined(__APPLE__) #include #if TARGET_CPU_ARM #define DISABLE_PROTECTION #define STATE_FLAVOR ARM_THREAD_STATE32 #define STATE_FLAVOR_COUNT ARM_THREAD_STATE32_COUNT #elif TARGET_CPU_ARM64 #define STATE_FLAVOR ARM_THREAD_STATE64 #define STATE_FLAVOR_COUNT ARM_THREAD_STATE64_COUNT #elif TARGET_CPU_X86 #define STATE_FLAVOR x86_THREAD_STATE32 #define STATE_FLAVOR_COUNT x86_THREAD_STATE32_COUNT #elif TARGET_CPU_X86_64 #define STATE_FLAVOR x86_THREAD_STATE64 #define STATE_FLAVOR_COUNT x86_THREAD_STATE64_COUNT #else #error Unsupported CPU architecture #endif #endif static CEeExecutor* g_eeExecutor = nullptr; CEeExecutor::CEeExecutor(CMIPS& context, uint8* ram) : CGenericMipsExecutor(context, 0x20000000) , m_ram(ram) { m_pageSize = framework_getpagesize(); } void CEeExecutor::AddExceptionHandler() { assert(g_eeExecutor == nullptr); g_eeExecutor = this; #ifdef DISABLE_PROTECTION return; #endif #if defined(_WIN32) m_handler = AddVectoredExceptionHandler(TRUE, &CEeExecutor::HandleException); assert(m_handler != NULL); #elif defined(__unix__) || defined(__ANDROID__) struct sigaction sigAction; sigAction.sa_handler = nullptr; sigAction.sa_sigaction = &HandleException; sigAction.sa_flags = SA_SIGINFO; sigemptyset(&sigAction.sa_mask); int result = sigaction(SIGSEGV, &sigAction, nullptr); assert(result >= 0); #elif defined(__APPLE__) if(!m_running) { kern_return_t result = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &m_port); assert(result == KERN_SUCCESS); m_running = true; m_handlerThread = std::thread([this]() { HandlerThreadProc(); }); } AttachExceptionHandlerToThread(); #endif } void CEeExecutor::RemoveExceptionHandler() { #ifndef DISABLE_PROTECTION #if defined(_WIN32) RemoveVectoredExceptionHandler(m_handler); #elif defined(__APPLE__) m_running = false; m_handlerThread.join(); #endif #endif //!DISABLE_PROTECTION g_eeExecutor = nullptr; } void CEeExecutor::AttachExceptionHandlerToThread() { //Only necessary for macOS and iOS since the handler is set on a thread basis #if defined(__APPLE__) assert(m_running); auto result = mach_port_insert_right(mach_task_self(), m_port, m_port, MACH_MSG_TYPE_MAKE_SEND); assert(result == KERN_SUCCESS); result = thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, m_port, EXCEPTION_STATE | MACH_EXCEPTION_CODES, STATE_FLAVOR); assert(result == KERN_SUCCESS); result = mach_port_mod_refs(mach_task_self(), m_port, MACH_PORT_RIGHT_SEND, -1); assert(result == KERN_SUCCESS); #endif } void CEeExecutor::Reset() { SetMemoryProtected(m_ram, PS2::EE_RAM_SIZE, false); m_cachedBlocks.clear(); CGenericMipsExecutor::Reset(); } void CEeExecutor::ClearActiveBlocksInRange(uint32 start, uint32 end, bool executing) { uint32 rangeSize = end - start; SetMemoryProtected(m_ram + start, rangeSize, false); CGenericMipsExecutor::ClearActiveBlocksInRange(start, end, executing); } BasicBlockPtr CEeExecutor::BlockFactory(CMIPS& context, uint32 start, uint32 end) { uint32 blockSize = (end - start) + 4; //Kernel area is below 0x100000 and isn't protected. Some games will write code in there //but it is safe to assume that it won't change (code writes some data just besides itself //so it keeps generating exceptions, making the game slower) if(start >= 0x100000 && start < PS2::EE_RAM_SIZE) { SetMemoryProtected(m_ram + start, blockSize, true); } auto blockMemory = reinterpret_cast(alloca(blockSize)); for(uint32 address = start; address <= end; address += 4) { uint32 index = (address - start) / 4; uint32 opcode = m_context.m_pMemoryMap->GetInstruction(address); blockMemory[index] = opcode; } uint32 checksum = crc32(0, reinterpret_cast(blockMemory), blockSize); auto blockKey = std::make_tuple(checksum, start, end); bool hasBreakpoint = m_context.HasBreakpointInRange(start, end); if(!hasBreakpoint) { auto blockIterator = m_cachedBlocks.find(blockKey); if(blockIterator != std::end(m_cachedBlocks)) { const auto& basicBlock(blockIterator->second); uint32 recycleCount = basicBlock->GetRecycleCount(); basicBlock->SetRecycleCount(std::min(RECYCLE_NOLINK_THRESHOLD, recycleCount + 1)); return basicBlock; } } auto result = std::make_shared(context, start, end); result->Compile(); if(!hasBreakpoint) { m_cachedBlocks.insert(std::make_pair(blockKey, result)); } return result; } bool CEeExecutor::HandleAccessFault(intptr_t ptr) { ptrdiff_t addr = reinterpret_cast(ptr) - m_ram; if(addr >= 0 && addr < PS2::EE_RAM_SIZE) { addr &= ~(m_pageSize - 1); ClearActiveBlocksInRange(addr, addr + m_pageSize, true); return true; } return false; } void CEeExecutor::SetMemoryProtected(void* addr, size_t size, bool protect) { #ifdef DISABLE_PROTECTION return; #endif #if defined(_WIN32) DWORD oldProtect = 0; BOOL result = VirtualProtect(addr, size, protect ? PAGE_READONLY : PAGE_READWRITE, &oldProtect); assert(result == TRUE); #elif defined(__unix__) || defined(__ANDROID__) || defined(__APPLE__) uintptr_t addrValue = reinterpret_cast(addr) & ~(m_pageSize - 1); addr = reinterpret_cast(addrValue); size = size + (m_pageSize - 1) & ~(m_pageSize - 1); int result = mprotect(addr, size, protect ? PROT_READ : PROT_READ | PROT_WRITE); assert(result >= 0); #else assert(false); #endif } #if defined(_WIN32) LONG WINAPI CEeExecutor::HandleException(_EXCEPTION_POINTERS* exceptionInfo) { return g_eeExecutor->HandleExceptionInternal(exceptionInfo); } LONG CEeExecutor::HandleExceptionInternal(_EXCEPTION_POINTERS* exceptionInfo) { auto exceptionRecord = exceptionInfo->ExceptionRecord; if(exceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { if(HandleAccessFault(exceptionRecord->ExceptionInformation[1])) { return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; } #elif defined(__unix__) || defined(__ANDROID__) void CEeExecutor::HandleException(int sigId, siginfo_t* sigInfo, void* baseContext) { g_eeExecutor->HandleExceptionInternal(sigId, sigInfo, baseContext); } void CEeExecutor::HandleExceptionInternal(int sigId, siginfo_t* sigInfo, void* baseContext) { if(sigId != SIGSEGV) return; if(HandleAccessFault(reinterpret_cast(sigInfo->si_addr))) { return; } signal(SIGSEGV, SIG_DFL); } #elif defined(__APPLE__) void CEeExecutor::HandlerThreadProc() { #pragma pack(push, 4) struct INPUT_MESSAGE { mach_msg_header_t head; NDR_record_t ndr; exception_type_t exception; mach_msg_type_number_t codeCount; intptr_t code[2]; int flavor; mach_msg_type_number_t stateCount; natural_t state[STATE_FLAVOR_COUNT]; mach_msg_trailer_t trailer; }; struct OUTPUT_MESSAGE { mach_msg_header_t head; NDR_record_t ndr; kern_return_t result; int flavor; mach_msg_type_number_t stateCount; natural_t state[STATE_FLAVOR_COUNT]; }; #pragma pack(pop) while(m_running) { kern_return_t result = KERN_SUCCESS; INPUT_MESSAGE inMsg; result = mach_msg(&inMsg.head, MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT, 0, sizeof(inMsg), m_port, 1000, MACH_PORT_NULL); if(result == MACH_RCV_TIMED_OUT) continue; assert(result == KERN_SUCCESS); assert(inMsg.head.msgh_id == 2406); //MACH_EXCEPTION_RAISE_RPC assert(inMsg.flavor == STATE_FLAVOR); assert(inMsg.stateCount == STATE_FLAVOR_COUNT); bool success = HandleAccessFault(inMsg.code[1]); OUTPUT_MESSAGE outMsg; outMsg.head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(inMsg.head.msgh_bits), 0); outMsg.head.msgh_remote_port = inMsg.head.msgh_remote_port; outMsg.head.msgh_local_port = MACH_PORT_NULL; outMsg.head.msgh_id = inMsg.head.msgh_id + 100; outMsg.head.msgh_size = sizeof(outMsg); outMsg.ndr = inMsg.ndr; if(success) { outMsg.result = KERN_SUCCESS; outMsg.flavor = STATE_FLAVOR; outMsg.stateCount = STATE_FLAVOR_COUNT; memcpy(outMsg.state, inMsg.state, STATE_FLAVOR_COUNT * sizeof(natural_t)); } else { outMsg.result = KERN_FAILURE; outMsg.flavor = 0; outMsg.stateCount = 0; } result = mach_msg(&outMsg.head, MACH_SEND_MSG | MACH_RCV_LARGE, sizeof(outMsg), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); assert(result == KERN_SUCCESS); } } #endif