From 13a8fde3ad2a4a37cf1bb8dcb367b4c8fc8b4d9b Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Sun, 7 Jul 2019 09:42:54 -0700 Subject: Implement MapPhysicalMemory/UnmapPhysicalMemory This implements svcMapPhysicalMemory/svcUnmapPhysicalMemory for Yuzu, which can be used to map memory at a desired address by games since 3.0.0. It also properly parses SystemResourceSize from NPDM, and makes information available via svcGetInfo. This is needed for games like Super Smash Bros. and Diablo 3 -- this PR's implementation does not run into the "ASCII reads" issue mentioned in the comments of #2626, which was caused by the following bugs in Yuzu's memory management that this PR also addresses: * Yuzu's memory coalescing does not properly merge blocks. This results in a polluted address space/svcQueryMemory results that would be impossible to replicate on hardware, which can lead to game code making the wrong assumptions about memory layout. * This implements better merging for AllocatedMemoryBlocks. * Yuzu's implementation of svcMirrorMemory unprotected the entire virtual memory range containing the range being mirrored. This could lead to games attempting to map data at that unprotected range/attempting to access that range after yuzu improperly unmapped it. * This PR fixes it by simply calling ReprotectRange instead of Reprotect. --- src/core/hle/kernel/vm_manager.cpp | 320 ++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 8 deletions(-) (limited to 'src/core/hle/kernel/vm_manager.cpp') diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 501544090..9385a8697 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -12,6 +12,8 @@ #include "core/core.h" #include "core/file_sys/program_metadata.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/vm_manager.h" #include "core/memory.h" #include "core/memory_setup.h" @@ -49,9 +51,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { type != next.type) { return false; } - if (type == VMAType::AllocatedMemoryBlock && - (backing_block != next.backing_block || offset + size != next.offset)) { - return false; + if (type == VMAType::AllocatedMemoryBlock) { + return true; } if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) { return false; @@ -100,7 +101,7 @@ bool VMManager::IsValidHandle(VMAHandle handle) const { ResultVal VMManager::MapMemoryBlock(VAddr target, std::shared_ptr> block, std::size_t offset, u64 size, - MemoryState state) { + MemoryState state, VMAPermission perm) { ASSERT(block != nullptr); ASSERT(offset + size <= block->size()); @@ -119,7 +120,7 @@ ResultVal VMManager::MapMemoryBlock(VAddr target, VMAPermission::ReadWriteExecute); final_vma.type = VMAType::AllocatedMemoryBlock; - final_vma.permissions = VMAPermission::ReadWrite; + final_vma.permissions = perm; final_vma.state = state; final_vma.backing_block = std::move(block); final_vma.offset = offset; @@ -308,6 +309,258 @@ ResultVal VMManager::SetHeapSize(u64 size) { return MakeResult(heap_region_base); } +ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { + const auto last_addr = target + size - 1; + VAddr cur_addr = target; + std::size_t mapped_size = 0; + + ResultCode result = RESULT_SUCCESS; + + // Check whether we've already mapped the desired memory. + { + auto vma = FindVMA(target); + ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end"); + + while (true) { + const auto vma_start = vma->second.base; + const auto vma_size = vma->second.size; + const auto state = vma->second.state; + + // Handle last block. + if (last_addr <= (vma_start + vma_size - 1)) { + if (state != MemoryState::Unmapped) { + mapped_size += last_addr - cur_addr + 1; + } + break; + } + + if (state != MemoryState::Unmapped) { + mapped_size += vma_start + vma_size - cur_addr; + } + cur_addr = vma_start + vma_size; + vma++; + ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end"); + } + + // If we already have the desired amount mapped, we're done. + if (mapped_size == size) { + return RESULT_SUCCESS; + } + } + + // Check that we can map the memory we want. + const auto res_limit = Core::CurrentProcess()->GetResourceLimit(); + const u64 physmem_remaining = res_limit->GetMaxResourceValue(ResourceType::PhysicalMemory) - + res_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory); + if (physmem_remaining < (size - mapped_size)) { + return ERR_RESOURCE_LIMIT_EXCEEDED; + } + + // Keep track of the memory regions we unmap. + std::vector> mapped_regions; + + // Iterate, trying to map memory. + // Map initially with VMAPermission::None. + { + cur_addr = target; + + auto vma = FindVMA(target); + ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end"); + + while (true) { + const auto vma_start = vma->second.base; + const auto vma_size = vma->second.size; + const auto state = vma->second.state; + + // Handle last block. + if (last_addr <= (vma_start + vma_size - 1)) { + if (state == MemoryState::Unmapped) { + const auto map_res = MapMemoryBlock( + cur_addr, std::make_shared>(last_addr - cur_addr + 1, 0), 0, + last_addr - cur_addr + 1, MemoryState::Heap, VMAPermission::None); + result = map_res.Code(); + if (result.IsSuccess()) { + mapped_regions.push_back( + std::make_pair(cur_addr, last_addr - cur_addr + 1)); + } + } + break; + } + + if (state == MemoryState::Unmapped) { + const auto map_res = MapMemoryBlock( + cur_addr, std::make_shared>(vma_start + vma_size - cur_addr, 0), + 0, vma_start + vma_size - cur_addr, MemoryState::Heap, VMAPermission::None); + result = map_res.Code(); + if (result.IsSuccess()) { + mapped_regions.push_back( + std::make_pair(cur_addr, vma_start + vma_size - cur_addr)); + } else { + break; + } + } + cur_addr = vma_start + vma_size; + vma = FindVMA(cur_addr); + ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end"); + } + } + + // If we failed, unmap memory. + if (result.IsError()) { + for (const auto& it : mapped_regions) { + const auto unmap_res = UnmapRange(it.first, it.second); + ASSERT_MSG(unmap_res.IsSuccess(), "MapPhysicalMemory un-map on error"); + } + + return result; + } + + // We didn't fail, so reprotect all the memory to ReadWrite. + { + cur_addr = target; + + auto vma = FindVMA(target); + ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end"); + + while (true) { + const auto vma_start = vma->second.base; + const auto vma_size = vma->second.size; + const auto state = vma->second.state; + const auto perm = vma->second.permissions; + + // Handle last block. + if (last_addr <= (vma_start + vma_size - 1)) { + if (state == MemoryState::Heap && perm == VMAPermission::None) { + ASSERT_MSG( + ReprotectRange(cur_addr, last_addr - cur_addr + 1, VMAPermission::ReadWrite) + .IsSuccess(), + "MapPhysicalMemory reprotect"); + } + break; + } + + if (state == MemoryState::Heap && perm == VMAPermission::None) { + ASSERT_MSG(ReprotectRange(cur_addr, vma_start + vma_size - cur_addr, + VMAPermission::ReadWrite) + .IsSuccess(), + "MapPhysicalMemory reprotect"); + } + cur_addr = vma_start + vma_size; + vma = FindVMA(cur_addr); + ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end"); + } + } + + // Update amount of mapped physical memory. + physical_memory_mapped += size - mapped_size; + + return RESULT_SUCCESS; +} + +ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { + auto last_addr = target + size - 1; + VAddr cur_addr = target; + std::size_t mapped_size = 0; + + ResultCode result = RESULT_SUCCESS; + + // Check how much of the memory is currently mapped. + { + auto vma = FindVMA(target); + ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end"); + + while (true) { + const auto vma_start = vma->second.base; + const auto vma_size = vma->second.size; + const auto state = vma->second.state; + const auto attr = vma->second.attribute; + + // Memory within region must be free or mapped heap. + if (!((state == MemoryState::Heap && attr == MemoryAttribute::None) || + (state == MemoryState::Unmapped))) { + return ERR_INVALID_ADDRESS_STATE; + } + + // If this is the last block and it's mapped, update mapped size. + if (last_addr <= (vma_start + vma_size - 1)) { + if (state == MemoryState::Heap) { + mapped_size += last_addr - cur_addr + 1; + } + break; + } + + if (state == MemoryState::Heap) { + mapped_size += vma_start + vma_size - cur_addr; + } + cur_addr = vma_start + vma_size; + vma++; + ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end"); + } + + // If memory is already unmapped, we're done. + if (mapped_size == 0) { + return RESULT_SUCCESS; + } + } + + // Keep track of the memory regions we unmap. + std::vector> unmapped_regions; + + // Try to unmap regions. + { + cur_addr = target; + + auto vma = FindVMA(target); + ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end"); + + while (true) { + const auto vma_start = vma->second.base; + const auto vma_size = vma->second.size; + const auto state = vma->second.state; + const auto perm = vma->second.permissions; + + // Handle last block. + if (last_addr <= (vma_start + vma_size - 1)) { + if (state == MemoryState::Heap) { + result = UnmapRange(cur_addr, last_addr - cur_addr + 1); + if (result.IsSuccess()) { + unmapped_regions.push_back( + std::make_pair(cur_addr, last_addr - cur_addr + 1)); + } + } + break; + } + + if (state == MemoryState::Heap) { + result = UnmapRange(cur_addr, vma_start + vma_size - cur_addr); + if (result.IsSuccess()) { + unmapped_regions.push_back( + std::make_pair(cur_addr, vma_start + vma_size - cur_addr)); + } else { + break; + } + } + + cur_addr = vma_start + vma_size; + vma = FindVMA(cur_addr); + ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end"); + } + } + + // If we failed, re-map regions. + // TODO: Preserve memory contents? + if (result.IsError()) { + for (const auto& it : unmapped_regions) { + const auto remap_res = + MapMemoryBlock(it.first, std::make_shared>(it.second, 0), 0, + it.second, MemoryState::Heap, VMAPermission::None); + ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error"); + } + } + + return RESULT_SUCCESS; +} + ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) { constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped; const auto src_check_result = CheckRangeState( @@ -455,7 +708,7 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem // Protect mirror with permissions from old region Reprotect(new_vma, vma->second.permissions); // Remove permissions from old region - Reprotect(vma, VMAPermission::None); + ReprotectRange(src_addr, size, VMAPermission::None); return RESULT_SUCCESS; } @@ -588,14 +841,14 @@ VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) { VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) { const VMAIter next_vma = std::next(iter); if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) { - iter->second.size += next_vma->second.size; + MergeAdjacentVMA(iter->second, next_vma->second); vma_map.erase(next_vma); } if (iter != vma_map.begin()) { VMAIter prev_vma = std::prev(iter); if (prev_vma->second.CanBeMergedWith(iter->second)) { - prev_vma->second.size += iter->second.size; + MergeAdjacentVMA(prev_vma->second, iter->second); vma_map.erase(iter); iter = prev_vma; } @@ -604,6 +857,57 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) { return iter; } +void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right) { + ASSERT(left.CanBeMergedWith(right)); + + // Always merge allocated memory blocks, even when they don't share the same backing block. + if (left.type == VMAType::AllocatedMemoryBlock && + (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { + // Check if we can save work. + if (left.offset == 0 && left.size == left.backing_block->size()) { + // Fast case: left is an entire backing block. + left.backing_block->insert(left.backing_block->end(), + right.backing_block->begin() + right.offset, + right.backing_block->begin() + right.offset + right.size); + } else { + // Slow case: make a new memory block for left and right. + auto new_memory = std::make_shared>(); + new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset, + left.backing_block->begin() + left.offset + left.size); + new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset, + right.backing_block->begin() + right.offset + right.size); + left.backing_block = new_memory; + left.offset = 0; + } + + // Page table update is needed, because backing memory changed. + left.size += right.size; + UpdatePageTableForVMA(left); + + // Update mappings for unicorn. + system.ArmInterface(0).UnmapMemory(left.base, left.size); + system.ArmInterface(1).UnmapMemory(left.base, left.size); + system.ArmInterface(2).UnmapMemory(left.base, left.size); + system.ArmInterface(3).UnmapMemory(left.base, left.size); + + system.ArmInterface(0).MapBackingMemory(left.base, left.size, + left.backing_block->data() + left.offset, + VMAPermission::ReadWriteExecute); + system.ArmInterface(1).MapBackingMemory(left.base, left.size, + left.backing_block->data() + left.offset, + VMAPermission::ReadWriteExecute); + system.ArmInterface(2).MapBackingMemory(left.base, left.size, + left.backing_block->data() + left.offset, + VMAPermission::ReadWriteExecute); + system.ArmInterface(3).MapBackingMemory(left.base, left.size, + left.backing_block->data() + left.offset, + VMAPermission::ReadWriteExecute); + } else { + // Just update the size. + left.size += right.size; + } +} + void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { switch (vma.type) { case VMAType::Free: -- cgit v1.2.3