From e4f35f70ac822ee6ea68b8890401f36891c2c6e6 Mon Sep 17 00:00:00 2001 From: Subv Date: Sun, 19 Nov 2017 11:24:23 -0500 Subject: [PATCH 1/2] Memory: Added a function to change the memory state of an address range. This will be useful when implementing memory aliasing operations. --- src/core/hle/kernel/vm_manager.cpp | 33 ++++++++++++++++++++++++++++++ src/core/hle/kernel/vm_manager.h | 17 ++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 7a007c065..3f1ae2c4a 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -129,6 +129,39 @@ ResultVal VMManager::MapMMIO(VAddr target, PAddr paddr, u3 return MakeResult(MergeAdjacent(vma_handle)); } +ResultCode VMManager::ChangeMemoryState(VAddr target, u32 size, MemoryState expected_state, + VMAPermission expected_perms, MemoryState new_state, + VMAPermission new_perms) { + VAddr target_end = target + size; + VMAIter begin_vma = StripIterConstness(FindVMA(target)); + VMAIter i_end = vma_map.lower_bound(target_end); + + if (begin_vma == vma_map.end()) + return ERR_INVALID_ADDRESS; + + for (auto i = begin_vma; i != i_end; ++i) { + auto& vma = i->second; + if (vma.meminfo_state != expected_state) { + return ERR_INVALID_ADDRESS_STATE; + } + u32 perms = static_cast(expected_perms); + if ((static_cast(vma.permissions) & perms) != perms) { + return ERR_INVALID_ADDRESS_STATE; + } + } + + CASCADE_RESULT(auto vma, CarveVMARange(target, size)); + ASSERT(vma->second.size == size); + + vma->second.permissions = new_perms; + vma->second.meminfo_state = new_state; + UpdatePageTableForVMA(vma->second); + + MergeAdjacent(vma); + + return RESULT_SUCCESS; +} + VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) { VirtualMemoryArea& vma = vma_handle->second; vma.type = VMAType::Free; diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 1302527bb..92fe987e2 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -166,6 +166,21 @@ public: ResultVal MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state, Memory::MMIORegionPointer mmio_handler); + /** + * Updates the memory state and permissions of the specified range. The range's original memory + * state and permissions must match the `expected` parameters. + * + * @param target The guest address of the beginning of the range. + * @param size The size of the range + * @param expected_state Expected MemoryState of the range. + * @param expected_perms Expected VMAPermission of the range. + * @param new_state New MemoryState for the range. + * @param new_perms New VMAPermission for the range. + */ + ResultCode ChangeMemoryState(VAddr target, u32 size, MemoryState expected_state, + VMAPermission expected_perms, MemoryState new_state, + VMAPermission new_perms); + /// Unmaps a range of addresses, splitting VMAs as necessary. ResultCode UnmapRange(VAddr target, u32 size); @@ -224,4 +239,4 @@ private: /// Updates the pages corresponding to this VMA so they match the VMA's attributes. void UpdatePageTableForVMA(const VirtualMemoryArea& vma); }; -} +} // namespace Kernel From 07089cfb3ca9b5767f4a5f11b3a5350648fafc8e Mon Sep 17 00:00:00 2001 From: Subv Date: Sun, 19 Nov 2017 11:25:25 -0500 Subject: [PATCH 2/2] Tests: Added some tests for the VMManager class. Covering basic operations like mapping, unmapping, reprotecting and changing memory state. --- src/tests/CMakeLists.txt | 1 + src/tests/core/memory/vm_manager.cpp | 138 +++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 src/tests/core/memory/vm_manager.cpp diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 1e0580825..ab67df21f 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(tests core/file_sys/path_parser.cpp core/hle/kernel/hle_ipc.cpp core/memory/memory.cpp + core/memory/vm_manager.cpp glad.cpp tests.cpp ) diff --git a/src/tests/core/memory/vm_manager.cpp b/src/tests/core/memory/vm_manager.cpp new file mode 100644 index 000000000..f3974291d --- /dev/null +++ b/src/tests/core/memory/vm_manager.cpp @@ -0,0 +1,138 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/vm_manager.h" +#include "core/memory.h" + +TEST_CASE("Memory Basics", "[kernel][memory]") { + auto block = std::make_shared>(Memory::PAGE_SIZE); + SECTION("mapping memory") { + // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. + auto manager = std::make_unique(); + auto result = manager->MapMemoryBlock(Memory::HEAP_VADDR, block, 0, block->size(), + Kernel::MemoryState::Private); + REQUIRE(result.Code() == RESULT_SUCCESS); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.size == block->size()); + CHECK(vma->second.type == Kernel::VMAType::AllocatedMemoryBlock); + CHECK(vma->second.backing_block == block); + CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private); + } + + SECTION("unmapping memory") { + // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. + auto manager = std::make_unique(); + auto result = manager->MapMemoryBlock(Memory::HEAP_VADDR, block, 0, block->size(), + Kernel::MemoryState::Private); + REQUIRE(result.Code() == RESULT_SUCCESS); + + ResultCode code = manager->UnmapRange(Memory::HEAP_VADDR, block->size()); + REQUIRE(code == RESULT_SUCCESS); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.type == Kernel::VMAType::Free); + CHECK(vma->second.backing_block == nullptr); + } + + SECTION("changing memory permissions") { + // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. + auto manager = std::make_unique(); + auto result = manager->MapMemoryBlock(Memory::HEAP_VADDR, block, 0, block->size(), + Kernel::MemoryState::Private); + REQUIRE(result.Code() == RESULT_SUCCESS); + + ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block->size(), + Kernel::VMAPermission::Execute); + CHECK(code == RESULT_SUCCESS); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.permissions == Kernel::VMAPermission::Execute); + + code = manager->UnmapRange(Memory::HEAP_VADDR, block->size()); + REQUIRE(code == RESULT_SUCCESS); + } + + SECTION("changing memory state") { + // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. + auto manager = std::make_unique(); + auto result = manager->MapMemoryBlock(Memory::HEAP_VADDR, block, 0, block->size(), + Kernel::MemoryState::Private); + REQUIRE(result.Code() == RESULT_SUCCESS); + + ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block->size(), + Kernel::VMAPermission::ReadWrite); + REQUIRE(code == RESULT_SUCCESS); + + SECTION("with invalid address") { + ResultCode code = manager->ChangeMemoryState( + 0xFFFFFFFF, block->size(), Kernel::MemoryState::Locked, + Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased, + Kernel::VMAPermission::Execute); + CHECK(code == Kernel::ERR_INVALID_ADDRESS); + } + + SECTION("ignoring the original permissions") { + ResultCode code = manager->ChangeMemoryState( + Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private, + Kernel::VMAPermission::None, Kernel::MemoryState::Locked, + Kernel::VMAPermission::Write); + CHECK(code == RESULT_SUCCESS); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.permissions == Kernel::VMAPermission::Write); + CHECK(vma->second.meminfo_state == Kernel::MemoryState::Locked); + } + + SECTION("enforcing the original permissions with correct expectations") { + ResultCode code = manager->ChangeMemoryState( + Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private, + Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased, + Kernel::VMAPermission::Execute); + CHECK(code == RESULT_SUCCESS); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.permissions == Kernel::VMAPermission::Execute); + CHECK(vma->second.meminfo_state == Kernel::MemoryState::Aliased); + } + + SECTION("with incorrect permission expectations") { + ResultCode code = manager->ChangeMemoryState( + Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private, + Kernel::VMAPermission::Execute, Kernel::MemoryState::Aliased, + Kernel::VMAPermission::Execute); + CHECK(code == Kernel::ERR_INVALID_ADDRESS_STATE); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.permissions == Kernel::VMAPermission::ReadWrite); + CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private); + } + + SECTION("with incorrect state expectations") { + ResultCode code = manager->ChangeMemoryState( + Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Locked, + Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased, + Kernel::VMAPermission::Execute); + CHECK(code == Kernel::ERR_INVALID_ADDRESS_STATE); + + auto vma = manager->FindVMA(Memory::HEAP_VADDR); + CHECK(vma != manager->vma_map.end()); + CHECK(vma->second.permissions == Kernel::VMAPermission::ReadWrite); + CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private); + } + + code = manager->UnmapRange(Memory::HEAP_VADDR, block->size()); + REQUIRE(code == RESULT_SUCCESS); + } +}