/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2010 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "common/Align.h" #include "common/PageFaultSource.h" #include "common/EventSource.inl" #include "common/MemsetFast.inl" #include "common/Console.h" #include "fmt/core.h" #include template class EventSource; SrcType_PageFault* Source_PageFault = NULL; std::mutex PageFault_Mutex; void pxInstallSignalHandler() { if (!Source_PageFault) { Source_PageFault = new SrcType_PageFault(); } _platform_InstallSignalHandler(); // NOP on Win32 systems -- we use __try{} __except{} instead. } // -------------------------------------------------------------------------------------- // EventListener_PageFault (implementations) // -------------------------------------------------------------------------------------- EventListener_PageFault::EventListener_PageFault() { pxAssert(Source_PageFault); Source_PageFault->Add(*this); } EventListener_PageFault::~EventListener_PageFault() { if (Source_PageFault) Source_PageFault->Remove(*this); } void SrcType_PageFault::Dispatch(const PageFaultInfo& params) { m_handled = false; _parent::Dispatch(params); } void SrcType_PageFault::_DispatchRaw(ListenerIterator iter, const ListenerIterator& iend, const PageFaultInfo& evt) { do { (*iter)->DispatchEvent(evt, m_handled); } while ((++iter != iend) && !m_handled); } // -------------------------------------------------------------------------------------- // VirtualMemoryManager (implementations) // -------------------------------------------------------------------------------------- VirtualMemoryManager::VirtualMemoryManager(std::string name, const char* file_mapping_name, uptr base, size_t size, uptr upper_bounds, bool strict) : m_name(std::move(name)) , m_file_handle(nullptr) , m_baseptr(0) , m_pageuse(nullptr) , m_pages_reserved(0) { if (!size) return; size_t reserved_bytes = Common::PageAlign(size); m_pages_reserved = reserved_bytes / __pagesize; if (file_mapping_name && file_mapping_name[0]) { std::string real_file_mapping_name(HostSys::GetFileMappingName(file_mapping_name)); m_file_handle = HostSys::CreateSharedMemory(real_file_mapping_name.c_str(), reserved_bytes); if (!m_file_handle) return; m_baseptr = static_cast(HostSys::MapSharedMemory(m_file_handle, 0, (void*)base, reserved_bytes, PageAccess_ReadWrite())); if (!m_baseptr || (upper_bounds != 0 && (((uptr)m_baseptr + reserved_bytes) > upper_bounds))) { DevCon.Warning("%s: host memory @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " is unavailable; attempting to map elsewhere...", m_name.c_str(), base, base + size); SafeSysMunmap(m_baseptr, reserved_bytes); if (base) { // Let's try again at an OS-picked memory area, and then hope it meets needed // boundschecking criteria below. m_baseptr = static_cast(HostSys::MapSharedMemory(m_file_handle, 0, nullptr, reserved_bytes, PageAccess_ReadWrite())); } } } else { m_baseptr = static_cast(HostSys::Mmap((void*)base, reserved_bytes, PageAccess_Any())); if (!m_baseptr || (upper_bounds != 0 && (((uptr)m_baseptr + reserved_bytes) > upper_bounds))) { DevCon.Warning("%s: host memory @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " is unavailable; attempting to map elsewhere...", m_name.c_str(), base, base + size); SafeSysMunmap(m_baseptr, reserved_bytes); if (base) { // Let's try again at an OS-picked memory area, and then hope it meets needed // boundschecking criteria below. m_baseptr = static_cast(HostSys::Mmap(0, reserved_bytes, PageAccess_Any())); } } } bool fulfillsRequirements = true; if (strict && (uptr)m_baseptr != base) fulfillsRequirements = false; if ((upper_bounds != 0) && ((uptr)(m_baseptr + reserved_bytes) > upper_bounds)) fulfillsRequirements = false; if (!fulfillsRequirements) { if (m_file_handle) { if (m_baseptr) HostSys::UnmapSharedMemory(m_baseptr, reserved_bytes); m_baseptr = 0; HostSys::DestroySharedMemory(m_file_handle); m_file_handle = nullptr; } else { SafeSysMunmap(m_baseptr, reserved_bytes); } } if (!m_baseptr) return; m_pageuse = new std::atomic[m_pages_reserved](); std::string mbkb; uint mbytes = reserved_bytes / _1mb; if (mbytes) mbkb = fmt::format("[{}mb]", mbytes); else mbkb = fmt::format("[{}kb]", reserved_bytes / 1024); DevCon.WriteLn(Color_Gray, "%-32s @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " %s", m_name.c_str(), m_baseptr, (uptr)m_baseptr + reserved_bytes, mbkb.c_str()); } VirtualMemoryManager::~VirtualMemoryManager() { if (m_pageuse) delete[] m_pageuse; if (m_baseptr) { if (m_file_handle) HostSys::UnmapSharedMemory((void*)m_baseptr, m_pages_reserved * __pagesize); else HostSys::Munmap(m_baseptr, m_pages_reserved * __pagesize); } if (m_file_handle) HostSys::DestroySharedMemory(m_file_handle); } static bool VMMMarkPagesAsInUse(std::atomic* begin, std::atomic* end) { for (auto current = begin; current < end; current++) { bool expected = false; if (!current->compare_exchange_strong(expected, true), std::memory_order_relaxed) { // This was already allocated! Undo the things we've set until this point while (--current >= begin) { if (!current->compare_exchange_strong(expected, false, std::memory_order_relaxed)) { // In the time we were doing this, someone set one of the things we just set to true back to false // This should never happen, but if it does we'll just stop and hope nothing bad happens pxAssert(0); return false; } } return false; } } return true; } u8* VirtualMemoryManager::Alloc(uptr offsetLocation, size_t size) const { size = Common::PageAlign(size); if (!pxAssertDev(offsetLocation % __pagesize == 0, "(VirtualMemoryManager) alloc at unaligned offsetLocation")) return nullptr; if (!pxAssertDev(size + offsetLocation <= m_pages_reserved * __pagesize, "(VirtualMemoryManager) alloc outside reserved area")) return nullptr; if (m_baseptr == 0) return nullptr; auto puStart = &m_pageuse[offsetLocation / __pagesize]; auto puEnd = &m_pageuse[(offsetLocation + size) / __pagesize]; if (!pxAssertDev(VMMMarkPagesAsInUse(puStart, puEnd), "(VirtualMemoryManager) allocation requests overlapped")) return nullptr; return m_baseptr + offsetLocation; } void VirtualMemoryManager::Free(void* address, size_t size) const { uptr offsetLocation = (uptr)address - (uptr)m_baseptr; if (!pxAssertDev(offsetLocation % __pagesize == 0, "(VirtualMemoryManager) free at unaligned address")) { uptr newLoc = Common::PageAlign(offsetLocation); size -= (offsetLocation - newLoc); offsetLocation = newLoc; } if (!pxAssertDev(size % __pagesize == 0, "(VirtualMemoryManager) free with unaligned size")) size -= size % __pagesize; if (!pxAssertDev(size + offsetLocation <= m_pages_reserved * __pagesize, "(VirtualMemoryManager) free outside reserved area")) return; auto puStart = &m_pageuse[offsetLocation / __pagesize]; auto puEnd = &m_pageuse[(offsetLocation + size) / __pagesize]; for (; puStart < puEnd; puStart++) { bool expected = true; if (!puStart->compare_exchange_strong(expected, false, std::memory_order_relaxed)) { pxAssertDev(0, "(VirtaulMemoryManager) double-free"); } } } // -------------------------------------------------------------------------------------- // VirtualMemoryBumpAllocator (implementations) // -------------------------------------------------------------------------------------- VirtualMemoryBumpAllocator::VirtualMemoryBumpAllocator(VirtualMemoryManagerPtr allocator, uptr offsetLocation, size_t size) : m_allocator(std::move(allocator)) , m_baseptr(m_allocator->Alloc(offsetLocation, size)) , m_endptr(m_baseptr + size) { if (m_baseptr.load() == 0) pxAssertDev(0, "(VirtualMemoryBumpAllocator) tried to construct from bad VirtualMemoryManager"); } u8* VirtualMemoryBumpAllocator::Alloc(size_t size) { if (m_baseptr.load() == 0) // True if constructed from bad VirtualMemoryManager (assertion was on initialization) return nullptr; size_t reservedSize = Common::PageAlign(size); u8* out = m_baseptr.fetch_add(reservedSize, std::memory_order_relaxed); if (!pxAssertDev(out - reservedSize + size <= m_endptr, "(VirtualMemoryBumpAllocator) ran out of memory")) return nullptr; return out; } // -------------------------------------------------------------------------------------- // VirtualMemoryReserve (implementations) // -------------------------------------------------------------------------------------- VirtualMemoryReserve::VirtualMemoryReserve(std::string name) : m_name(std::move(name)) { } VirtualMemoryReserve::~VirtualMemoryReserve() { pxAssertRel(!m_baseptr, "VirtualMemoryReserve has not been released."); } // Notes: // * This method should be called if the object is already in an released (unreserved) state. // Subsequent calls will be ignored, and the existing reserve will be returned. // // Parameters: // baseptr - the new base pointer that's about to be assigned // size - size of the region pointed to by baseptr // void VirtualMemoryReserve::Assign(VirtualMemoryManagerPtr allocator, u8* baseptr, size_t size) { pxAssertRel(size > 0 && Common::IsAlignedPow2(size, __pagesize), "VM allocation is not page aligned"); pxAssertRel(!m_baseptr, "Virtual memory reserve has already been assigned"); m_allocator = std::move(allocator); m_baseptr = baseptr; m_size = size; std::string mbkb; uint mbytes = size / _1mb; if (mbytes) mbkb = fmt::format("[{}mb]", mbytes); else mbkb = fmt::format("[{}kb]", size / 1024); DevCon.WriteLn(Color_Gray, "%-32s @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " %s", m_name.c_str(), m_baseptr, (uptr)m_baseptr + size, mbkb.c_str()); } u8* VirtualMemoryReserve::BumpAllocate(VirtualMemoryBumpAllocator& allocator, size_t size) { u8* base = allocator.Alloc(size); if (base) Assign(allocator.GetAllocator(), base, size); return base; } void VirtualMemoryReserve::Release() { if (!m_baseptr) return; m_allocator->Free(m_baseptr, m_size); m_baseptr = nullptr; m_size = 0; } // -------------------------------------------------------------------------------------- // PageProtectionMode (implementations) // -------------------------------------------------------------------------------------- std::string PageProtectionMode::ToString() const { std::string modeStr; if (m_read) modeStr += "Read"; if (m_write) modeStr += "Write"; if (m_exec) modeStr += "Exec"; if (modeStr.empty()) return "NoAccess"; if (modeStr.length() <= 5) modeStr += "Only"; return modeStr; }