mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-10-31 16:09:03 -05:00 
			
		
		
		
	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.
This commit is contained in:
		| @@ -172,6 +172,7 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { | ||||
|     program_id = metadata.GetTitleID(); | ||||
|     ideal_core = metadata.GetMainThreadCore(); | ||||
|     is_64bit_process = metadata.Is64BitProgram(); | ||||
|     system_resource_size = metadata.GetSystemResourceSize(); | ||||
|  | ||||
|     vm_manager.Reset(metadata.GetAddressSpaceType()); | ||||
|  | ||||
|   | ||||
| @@ -168,8 +168,9 @@ public: | ||||
|         return capabilities.GetPriorityMask(); | ||||
|     } | ||||
|  | ||||
|     u32 IsVirtualMemoryEnabled() const { | ||||
|         return is_virtual_address_memory_enabled; | ||||
|     /// Gets the amount of secure memory to allocate for memory management. | ||||
|     u32 GetSystemResourceSize() const { | ||||
|         return system_resource_size; | ||||
|     } | ||||
|  | ||||
|     /// Whether this process is an AArch64 or AArch32 process. | ||||
| @@ -298,12 +299,16 @@ private: | ||||
|     /// Title ID corresponding to the process | ||||
|     u64 program_id = 0; | ||||
|  | ||||
|     /// Specifies additional memory to be reserved for the process's memory management by the | ||||
|     /// system. When this is non-zero, secure memory is allocated and used for page table allocation | ||||
|     /// instead of using the normal global page tables/memory block management. | ||||
|     u32 system_resource_size = 0; | ||||
|  | ||||
|     /// Resource limit descriptor for this process | ||||
|     SharedPtr<ResourceLimit> resource_limit; | ||||
|  | ||||
|     /// The ideal CPU core for this process, threads are scheduled on this core by default. | ||||
|     u8 ideal_core = 0; | ||||
|     u32 is_virtual_address_memory_enabled = 0; | ||||
|  | ||||
|     /// The Thread Local Storage area is allocated as processes create threads, | ||||
|     /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part | ||||
|   | ||||
| @@ -729,8 +729,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha | ||||
|         StackRegionBaseAddr = 14, | ||||
|         StackRegionSize = 15, | ||||
|         // 3.0.0+ | ||||
|         IsVirtualAddressMemoryEnabled = 16, | ||||
|         PersonalMmHeapUsage = 17, | ||||
|         SystemResourceSize = 16, | ||||
|         SystemResourceUsage = 17, | ||||
|         TitleId = 18, | ||||
|         // 4.0.0+ | ||||
|         PrivilegedProcessId = 19, | ||||
| @@ -756,8 +756,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha | ||||
|     case GetInfoType::StackRegionSize: | ||||
|     case GetInfoType::TotalPhysicalMemoryAvailable: | ||||
|     case GetInfoType::TotalPhysicalMemoryUsed: | ||||
|     case GetInfoType::IsVirtualAddressMemoryEnabled: | ||||
|     case GetInfoType::PersonalMmHeapUsage: | ||||
|     case GetInfoType::SystemResourceSize: | ||||
|     case GetInfoType::SystemResourceUsage: | ||||
|     case GetInfoType::TitleId: | ||||
|     case GetInfoType::UserExceptionContextAddr: | ||||
|     case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap: | ||||
| @@ -822,8 +822,22 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha | ||||
|             *result = process->GetTotalPhysicalMemoryUsed(); | ||||
|             return RESULT_SUCCESS; | ||||
|  | ||||
|         case GetInfoType::IsVirtualAddressMemoryEnabled: | ||||
|             *result = process->IsVirtualMemoryEnabled(); | ||||
|         case GetInfoType::SystemResourceSize: | ||||
|             *result = process->GetSystemResourceSize(); | ||||
|             return RESULT_SUCCESS; | ||||
|  | ||||
|         case GetInfoType::SystemResourceUsage: | ||||
|             // On hardware, this returns the amount of system resource memory that has | ||||
|             // been used by the kernel. This is problematic for Yuzu to emulate, because | ||||
|             // system resource memory is used for page tables -- and yuzu doesn't really | ||||
|             // have a way to calculate how much memory is required for page tables for | ||||
|             // the current process at any given time. | ||||
|             // TODO: Is this even worth implementing? No game should ever use it, since | ||||
|             // the amount of remaining page table space should never be relevant except | ||||
|             // for diagnostics. Is returning a value other than zero wise? | ||||
|             LOG_WARNING(Kernel_SVC, | ||||
|                         "(STUBBED) Attempted to query system resource usage, returned 0"); | ||||
|             *result = 0; | ||||
|             return RESULT_SUCCESS; | ||||
|  | ||||
|         case GetInfoType::TitleId: | ||||
| @@ -946,6 +960,86 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Maps memory at a desired address | ||||
| static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { | ||||
|     LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); | ||||
|  | ||||
|     if (!Common::Is4KBAligned(addr)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
|  | ||||
|     if (!Common::Is4KBAligned(size)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); | ||||
|         return ERR_INVALID_SIZE; | ||||
|     } | ||||
|  | ||||
|     if (size == 0) { | ||||
|         LOG_ERROR(Kernel_SVC, "Size is zero"); | ||||
|         return ERR_INVALID_SIZE; | ||||
|     } | ||||
|  | ||||
|     if (!(addr < addr + size)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); | ||||
|         return ERR_INVALID_MEMORY_RANGE; | ||||
|     } | ||||
|  | ||||
|     auto* const current_process = Core::CurrentProcess(); | ||||
|     auto& vm_manager = current_process->VMManager(); | ||||
|  | ||||
|     if (current_process->GetSystemResourceSize() == 0) { | ||||
|         LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); | ||||
|         return ERR_INVALID_STATE; | ||||
|     } | ||||
|  | ||||
|     if (!vm_manager.IsWithinMapRegion(addr, size)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Range not within map region"); | ||||
|         return ERR_INVALID_MEMORY_RANGE; | ||||
|     } | ||||
|  | ||||
|     return vm_manager.MapPhysicalMemory(addr, size); | ||||
| } | ||||
|  | ||||
| /// Unmaps memory previously mapped via MapPhysicalMemory | ||||
| static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { | ||||
|     LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); | ||||
|  | ||||
|     if (!Common::Is4KBAligned(addr)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
|  | ||||
|     if (!Common::Is4KBAligned(size)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); | ||||
|         return ERR_INVALID_SIZE; | ||||
|     } | ||||
|  | ||||
|     if (size == 0) { | ||||
|         LOG_ERROR(Kernel_SVC, "Size is zero"); | ||||
|         return ERR_INVALID_SIZE; | ||||
|     } | ||||
|  | ||||
|     if (!(addr < addr + size)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); | ||||
|         return ERR_INVALID_MEMORY_RANGE; | ||||
|     } | ||||
|  | ||||
|     auto* const current_process = Core::CurrentProcess(); | ||||
|     auto& vm_manager = current_process->VMManager(); | ||||
|  | ||||
|     if (current_process->GetSystemResourceSize() == 0) { | ||||
|         LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); | ||||
|         return ERR_INVALID_STATE; | ||||
|     } | ||||
|  | ||||
|     if (!vm_manager.IsWithinMapRegion(addr, size)) { | ||||
|         LOG_ERROR(Kernel_SVC, "Range not within map region"); | ||||
|         return ERR_INVALID_MEMORY_RANGE; | ||||
|     } | ||||
|  | ||||
|     return vm_manager.UnmapPhysicalMemory(addr, size); | ||||
| } | ||||
|  | ||||
| /// Sets the thread activity | ||||
| static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) { | ||||
|     LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity); | ||||
| @@ -2303,8 +2397,8 @@ static const FunctionDef SVC_Table[] = { | ||||
|     {0x29, SvcWrap<GetInfo>, "GetInfo"}, | ||||
|     {0x2A, nullptr, "FlushEntireDataCache"}, | ||||
|     {0x2B, nullptr, "FlushDataCache"}, | ||||
|     {0x2C, nullptr, "MapPhysicalMemory"}, | ||||
|     {0x2D, nullptr, "UnmapPhysicalMemory"}, | ||||
|     {0x2C, SvcWrap<MapPhysicalMemory>, "MapPhysicalMemory"}, | ||||
|     {0x2D, SvcWrap<UnmapPhysicalMemory>, "UnmapPhysicalMemory"}, | ||||
|     {0x2E, nullptr, "GetFutureThreadInfo"}, | ||||
|     {0x2F, nullptr, "GetLastThreadInfo"}, | ||||
|     {0x30, SvcWrap<GetResourceLimitLimitValue>, "GetResourceLimitLimitValue"}, | ||||
|   | ||||
| @@ -32,6 +32,11 @@ void SvcWrap(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0)).raw); | ||||
| } | ||||
|  | ||||
| template <ResultCode func(Core::System&, u64, u64)> | ||||
| void SvcWrap(Core::System& system) { | ||||
|     FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw); | ||||
| } | ||||
|  | ||||
| template <ResultCode func(Core::System&, u32)> | ||||
| void SvcWrap(Core::System& system) { | ||||
|     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw); | ||||
|   | ||||
| @@ -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::VMAHandle> VMManager::MapMemoryBlock(VAddr target, | ||||
|                                                           std::shared_ptr<std::vector<u8>> 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::VMAHandle> 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<VAddr> VMManager::SetHeapSize(u64 size) { | ||||
|     return MakeResult<VAddr>(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<std::pair<u64, u64>> 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<std::vector<u8>>(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<std::vector<u8>>(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<std::pair<u64, u64>> 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<std::vector<u8>>(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<std::vector<u8>>(); | ||||
|             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: | ||||
|   | ||||
| @@ -349,7 +349,8 @@ public: | ||||
|      * @param state MemoryState tag to attach to the VMA. | ||||
|      */ | ||||
|     ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, | ||||
|                                         std::size_t offset, u64 size, MemoryState state); | ||||
|                                         std::size_t offset, u64 size, MemoryState state, | ||||
|                                         VMAPermission perm = VMAPermission::ReadWrite); | ||||
|  | ||||
|     /** | ||||
|      * Maps an unmanaged host memory pointer at a given address. | ||||
| @@ -450,6 +451,34 @@ public: | ||||
|     /// | ||||
|     ResultVal<VAddr> SetHeapSize(u64 size); | ||||
|  | ||||
|     /// Maps memory at a given address. | ||||
|     /// | ||||
|     /// @param addr The virtual address to map memory at. | ||||
|     /// @param size The amount of memory to map. | ||||
|     /// | ||||
|     /// @note The destination address must lie within the Map region. | ||||
|     /// | ||||
|     /// @note This function requires SystemResourceSize is non-zero, | ||||
|     ///       however, this is just because if it were not then the | ||||
|     ///       resulting page tables could be exploited on hardware by | ||||
|     ///       a malicious program. SystemResource usage does not need | ||||
|     ///       to be explicitly checked or updated here. | ||||
|     ResultCode MapPhysicalMemory(VAddr target, u64 size); | ||||
|  | ||||
|     /// Unmaps memory at a given address. | ||||
|     /// | ||||
|     /// @param addr The virtual address to unmap memory at. | ||||
|     /// @param size The amount of memory to unmap. | ||||
|     /// | ||||
|     /// @note The destination address must lie within the Map region. | ||||
|     /// | ||||
|     /// @note This function requires SystemResourceSize is non-zero, | ||||
|     ///       however, this is just because if it were not then the | ||||
|     ///       resulting page tables could be exploited on hardware by | ||||
|     ///       a malicious program. SystemResource usage does not need | ||||
|     ///       to be explicitly checked or updated here. | ||||
|     ResultCode UnmapPhysicalMemory(VAddr target, u64 size); | ||||
|  | ||||
|     /// Maps a region of memory as code memory. | ||||
|     /// | ||||
|     /// @param dst_address The base address of the region to create the aliasing memory region. | ||||
| @@ -657,6 +686,11 @@ private: | ||||
|      */ | ||||
|     VMAIter MergeAdjacent(VMAIter vma); | ||||
|  | ||||
|     /** | ||||
|      * Merges two adjacent VMAs. | ||||
|      */ | ||||
|     void MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right); | ||||
|  | ||||
|     /// Updates the pages corresponding to this VMA so they match the VMA's attributes. | ||||
|     void UpdatePageTableForVMA(const VirtualMemoryArea& vma); | ||||
|  | ||||
| @@ -742,6 +776,11 @@ private: | ||||
|     // end of the range. This is essentially 'base_address + current_size'. | ||||
|     VAddr heap_end = 0; | ||||
|  | ||||
|     // The current amount of memory mapped via MapPhysicalMemory. | ||||
|     // This is used here (and in Nintendo's kernel) only for debugging, and does not impact | ||||
|     // any behavior. | ||||
|     u64 physical_memory_mapped = 0; | ||||
|  | ||||
|     Core::System& system; | ||||
| }; | ||||
| } // namespace Kernel | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Scire
					Michael Scire