mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-27 01:02:48 +01: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:
parent
9e689a81f8
commit
13a8fde3ad
8 changed files with 475 additions and 21 deletions
|
@ -94,6 +94,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
|
|||
return aci_file_access.permissions;
|
||||
}
|
||||
|
||||
u32 ProgramMetadata::GetSystemResourceSize() const {
|
||||
return npdm_header.system_resource_size;
|
||||
}
|
||||
|
||||
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
|
||||
return aci_kernel_capabilities;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ public:
|
|||
u32 GetMainThreadStackSize() const;
|
||||
u64 GetTitleID() const;
|
||||
u64 GetFilesystemPermissions() const;
|
||||
u32 GetSystemResourceSize() const;
|
||||
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
|
||||
|
||||
void Print() const;
|
||||
|
@ -76,7 +77,8 @@ private:
|
|||
u8 reserved_3;
|
||||
u8 main_thread_priority;
|
||||
u8 main_thread_cpu;
|
||||
std::array<u8, 8> reserved_4;
|
||||
std::array<u8, 4> reserved_4;
|
||||
u32_le system_resource_size;
|
||||
u32_le process_category;
|
||||
u32_le main_stack_size;
|
||||
std::array<u8, 0x10> application_name;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue