From 85fa0942e7a5a7c1f0eae0b8bdf2a53020dcc266 Mon Sep 17 00:00:00 2001 From: Nekotekina Date: Mon, 27 Aug 2018 17:52:47 +0300 Subject: [PATCH] vm: allow 4k-aligned allocations for vm::stack Fix utils::shm::map logic for MapViewOfFileEx --- Utilities/VirtualMemory.cpp | 32 +++++++--- rpcs3/Emu/Cell/PPUThread.cpp | 2 +- rpcs3/Emu/Memory/vm.cpp | 115 ++++++++++++++++++++++++++--------- rpcs3/Emu/Memory/vm.h | 5 +- 4 files changed, 117 insertions(+), 37 deletions(-) diff --git a/Utilities/VirtualMemory.cpp b/Utilities/VirtualMemory.cpp index ad5de6d127..88e0774938 100644 --- a/Utilities/VirtualMemory.cpp +++ b/Utilities/VirtualMemory.cpp @@ -167,17 +167,35 @@ namespace utils u8* shm::map(void* ptr, protection prot) const { #ifdef _WIN32 - DWORD access = 0; + DWORD access = FILE_MAP_WRITE; switch (prot) { - case protection::rw: access = FILE_MAP_WRITE; break; - case protection::ro: access = FILE_MAP_READ; break; - case protection::no: break; - case protection::wx: access = FILE_MAP_WRITE | FILE_MAP_EXECUTE; break; - case protection::rx: access = FILE_MAP_READ | FILE_MAP_EXECUTE; break; + case protection::rw: + case protection::ro: + case protection::no: + break; + case protection::wx: + case protection::rx: + access |= FILE_MAP_EXECUTE; + break; } - return static_cast(::MapViewOfFileEx(m_handle, access, 0, 0, m_size, ptr)); + if (auto ret = static_cast(::MapViewOfFileEx(m_handle, access, 0, 0, m_size, ptr))) + { + if (prot != protection::rw && prot != protection::wx) + { + DWORD old; + if (!::VirtualProtect(ret, m_size, +prot, &old)) + { + ::UnmapViewOfFile(ret); + return nullptr; + } + } + + return ret; + } + + return nullptr; #else return static_cast(::mmap((void*)((u64)ptr & -0x10000), m_size, +prot, MAP_SHARED | (ptr ? MAP_FIXED : 0), m_file, 0)); #endif diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index d5fdcbedfa..679a5a3ae1 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -348,7 +348,7 @@ void ppu_thread::on_init(const std::shared_ptr& _this) if (!stack_addr) { // Allocate stack + gap between stacks - auto new_stack_base = vm::alloc(stack_size + 4096, vm::stack); + auto new_stack_base = vm::alloc(stack_size + 4096, vm::stack, 4096); if (!new_stack_base) { fmt::throw_exception("Out of stack memory (size=0x%x)" HERE, stack_size); diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index 7b02d8c637..8574204101 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -276,10 +276,8 @@ namespace vm // Memory pages std::array g_pages{}; - static void _page_map(u32 addr, u8 flags, utils::shm& shm) + static void _page_map(u32 addr, u8 flags, u32 size, utils::shm* shm) { - const u32 size = shm.size(); - if (!size || (size | addr) % 4096 || flags & page_allocated) { fmt::throw_exception("Invalid arguments (addr=0x%x, size=0x%x)" HERE, addr, size); @@ -293,7 +291,12 @@ namespace vm } } - if (shm.map_critical(g_base_addr + addr) != g_base_addr + addr) + if (!shm) + { + utils::memory_protect(g_base_addr + addr, size, utils::protection::rw); + std::memset(g_base_addr + addr, 0, size); + } + else if (shm->map_critical(g_base_addr + addr) != g_base_addr + addr) { fmt::throw_exception("Memory mapping failed - blame Windows (addr=0x%x, size=0x%x, flags=0x%x)", addr, size, flags); } @@ -375,28 +378,30 @@ namespace vm return true; } - static void _page_unmap(u32 addr, utils::shm& shm) + static u32 _page_unmap(u32 addr, u32 max_size, utils::shm* shm) { - const u32 size = shm.size(); - - if (!size || (size | addr) % 4096) + if (!max_size || (max_size | addr) % 4096) { - fmt::throw_exception("Invalid arguments (addr=0x%x, size=0x%x)" HERE, addr, size); + fmt::throw_exception("Invalid arguments (addr=0x%x, max_size=0x%x)" HERE, addr, max_size); } + // Determine deallocation size + u32 size = 0; bool is_exec = false; - for (u32 i = addr / 4096; i < addr / 4096 + size / 4096; i++) + for (u32 i = addr / 4096; i < addr / 4096 + max_size / 4096; i++) { if ((g_pages[i].flags & page_allocated) == 0) { - fmt::throw_exception("Memory not mapped (addr=0x%x, size=0x%x, current_addr=0x%x)" HERE, addr, size, i * 4096); + break; } if (g_pages[i].flags & page_executable) { is_exec = true; } + + size += 4096; } for (u32 i = addr / 4096; i < addr / 4096 + size / 4096; i++) @@ -407,7 +412,14 @@ namespace vm } } - shm.unmap_critical(g_base_addr + addr); + if (!shm) + { + utils::memory_protect(g_base_addr + addr, size, utils::protection::no); + } + else + { + shm->unmap_critical(g_base_addr + addr); + } if (is_exec) { @@ -418,6 +430,8 @@ namespace vm { utils::memory_decommit(g_stat_addr + addr, size); } + + return size; } bool check_addr(u32 addr, u32 size, u8 flags) @@ -486,10 +500,8 @@ namespace vm } } - bool block_t::try_alloc(u32 addr, u8 flags, std::shared_ptr&& shm) + bool block_t::try_alloc(u32 addr, u8 flags, u32 size, std::shared_ptr&& shm) { - const u32 size = shm->size(); - // Check if memory area is already mapped for (u32 i = addr / 4096; i <= (addr + size - 1) / 4096; i++) { @@ -500,7 +512,7 @@ namespace vm } // Map "real" memory pages - _page_map(addr, flags, *shm); + _page_map(addr, flags, size, shm.get()); // Add entry m_map[addr] = std::move(shm); @@ -539,6 +551,13 @@ namespace vm utils::memory_commit(g_reservations + 0xfff0000, 0x10000); utils::memory_commit(g_reservations2 + 0xfff0000, 0x10000); } + + if (flags & 0x100) + { + // Special path for 4k-aligned pages + m_common = std::make_shared(size); + verify(HERE), m_common->map_critical(vm::base(addr), utils::protection::no) == vm::base(addr); + } } block_t::~block_t() @@ -547,9 +566,18 @@ namespace vm vm::writer_lock lock(0); // Deallocate all memory - for (auto& entry : m_map) + for (auto it = m_map.begin(), end = m_map.end(); it != end;) { - _page_unmap(entry.first, *entry.second); + const auto next = std::next(it); + const auto size = (next == end ? this->addr + this->size : next->first) - it->first; + _page_unmap(it->first, size, it->second.get()); + it = next; + } + + // Special path for 4k-aligned pages + if (m_common) + { + m_common->unmap_critical(vm::base(addr)); } } @@ -564,11 +592,14 @@ namespace vm { vm::writer_lock lock(0); + // Determine minimal alignment + const u32 min_page_size = flags & 0x100 ? 0x1000 : 0x10000; + // Align to minimal page size - const u32 size = ::align(orig_size, 0x10000); + const u32 size = ::align(orig_size, min_page_size); // Check alignment (it's page allocation, so passing small values there is just silly) - if (align < 0x10000 || align != (0x80000000u >> cntlz32(align, true))) + if (align < min_page_size || align != (0x80000000u >> cntlz32(align, true))) { fmt::throw_exception("Invalid alignment (size=0x%x, align=0x%x)" HERE, size, align); } @@ -591,12 +622,19 @@ namespace vm } // Create or import shared memory object - std::shared_ptr shm = src ? std::shared_ptr(*src) : std::make_shared(size); + std::shared_ptr shm; + + if (m_common) + verify(HERE), !src; + else if (src) + shm = *src; + else + shm = std::make_shared(size); // Search for an appropriate place (unoptimized) for (u32 addr = ::align(this->addr, align); addr < this->addr + this->size - 1; addr += align) { - if (try_alloc(addr, pflags, std::move(shm))) + if (try_alloc(addr, pflags, size, std::move(shm))) { return addr; } @@ -609,8 +647,11 @@ namespace vm { vm::writer_lock lock(0); - // align to minimal page size - const u32 size = ::align(orig_size, 0x10000); + // Determine minimal alignment + const u32 min_page_size = flags & 0x100 ? 0x1000 : 0x10000; + + // Align to minimal page size + const u32 size = ::align(orig_size, min_page_size); // return if addr or size is invalid if (!size || size > this->size || addr < this->addr || addr + size - 1 > this->addr + this->size - 1) @@ -629,7 +670,17 @@ namespace vm pflags |= page_64k_size; } - if (!try_alloc(addr, pflags, src ? std::shared_ptr(*src) : std::make_shared(size))) + // Create or import shared memory object + std::shared_ptr shm; + + if (m_common) + verify(HERE), !src; + else if (src) + shm = *src; + else + shm = std::make_shared(size); + + if (!try_alloc(addr, pflags, size, std::move(shm))) { return 0; } @@ -655,10 +706,12 @@ namespace vm return 0; } - result = found->second->size(); + // Approximate allocation size + const auto next = std::next(found); + const auto size = (next == m_map.end() ? this->addr + this->size : next->first) - found->first; // Unmap "real" memory pages - _page_unmap(addr, *found->second); + result = _page_unmap(addr, size, found->second.get()); // Remove entry m_map.erase(found); @@ -697,6 +750,12 @@ namespace vm return {addr, nullptr}; } + // Special path + if (m_common) + { + return {this->addr, m_common}; + } + // Range check if (std::max(size, addr - found->first + size) > found->second->size()) { @@ -896,7 +955,7 @@ namespace vm std::make_shared(0x20000000, 0x10000000, 0x201), // user 64k pages nullptr, // user 1m pages std::make_shared(0xC0000000, 0x10000000), // video - std::make_shared(0xD0000000, 0x10000000), // stack + std::make_shared(0xD0000000, 0x10000000, 0x101), // stack std::make_shared(0xE0000000, 0x20000000), // SPU reserved }; } diff --git a/rpcs3/Emu/Memory/vm.h b/rpcs3/Emu/Memory/vm.h index 66db1be3cb..062d557d7c 100644 --- a/rpcs3/Emu/Memory/vm.h +++ b/rpcs3/Emu/Memory/vm.h @@ -146,7 +146,10 @@ namespace vm // Mapped regions: addr -> shm handle std::map> m_map; - bool try_alloc(u32 addr, u8 flags, std::shared_ptr&&); + // Common mapped region for special cases + std::shared_ptr m_common; + + bool try_alloc(u32 addr, u8 flags, u32 size, std::shared_ptr&&); public: block_t(u32 addr, u32 size, u64 flags = 0);