diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/hle/kernel/code_set.h | 3 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.cpp | 14 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.h | 5 | ||||
-rw-r--r-- | src/core/hle/kernel/physical_memory.h | 19 | ||||
-rw-r--r-- | src/core/hle/kernel/process.cpp | 184 | ||||
-rw-r--r-- | src/core/hle/kernel/process.h | 65 | ||||
-rw-r--r-- | src/core/hle/kernel/shared_memory.cpp | 6 | ||||
-rw-r--r-- | src/core/hle/kernel/shared_memory.h | 13 | ||||
-rw-r--r-- | src/core/hle/kernel/svc.cpp | 166 | ||||
-rw-r--r-- | src/core/hle/kernel/svc_wrap.h | 5 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.cpp | 10 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.h | 16 | ||||
-rw-r--r-- | src/core/hle/kernel/transfer_memory.cpp | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/transfer_memory.h | 3 | ||||
-rw-r--r-- | src/core/hle/kernel/vm_manager.cpp | 401 | ||||
-rw-r--r-- | src/core/hle/kernel/vm_manager.h | 108 |
16 files changed, 788 insertions, 232 deletions
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h index 879957dcb..d8ad54030 100644 --- a/src/core/hle/kernel/code_set.h +++ b/src/core/hle/kernel/code_set.h @@ -8,6 +8,7 @@ #include <vector> #include "common/common_types.h" +#include "core/hle/kernel/physical_memory.h" namespace Kernel { @@ -77,7 +78,7 @@ struct CodeSet final { } /// The overall data that backs this code set. - std::vector<u8> memory; + Kernel::PhysicalMemory memory; /// The segments that comprise this code set. std::array<Segment, 3> segments; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 757e5f21f..799e5e0d8 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -99,7 +99,8 @@ struct KernelCore::Impl { void Shutdown() { next_object_id = 0; - next_process_id = Process::ProcessIDMin; + next_kernel_process_id = Process::InitialKIPIDMin; + next_user_process_id = Process::ProcessIDMin; next_thread_id = 1; process_list.clear(); @@ -132,7 +133,8 @@ struct KernelCore::Impl { } std::atomic<u32> next_object_id{0}; - std::atomic<u64> next_process_id{Process::ProcessIDMin}; + std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin}; + std::atomic<u64> next_user_process_id{Process::ProcessIDMin}; std::atomic<u64> next_thread_id{1}; // Lists all processes that exist in the current session. @@ -226,8 +228,12 @@ u64 KernelCore::CreateNewThreadID() { return impl->next_thread_id++; } -u64 KernelCore::CreateNewProcessID() { - return impl->next_process_id++; +u64 KernelCore::CreateNewKernelProcessID() { + return impl->next_kernel_process_id++; +} + +u64 KernelCore::CreateNewUserProcessID() { + return impl->next_user_process_id++; } Core::Timing::EventType* KernelCore::ThreadWakeupCallbackEventType() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 6b8738599..0cc44ee76 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -96,7 +96,10 @@ private: u32 CreateNewObjectID(); /// Creates a new process ID, incrementing the internal process ID counter; - u64 CreateNewProcessID(); + u64 CreateNewKernelProcessID(); + + /// Creates a new process ID, incrementing the internal process ID counter; + u64 CreateNewUserProcessID(); /// Creates a new thread ID, incrementing the internal thread ID counter. u64 CreateNewThreadID(); diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h new file mode 100644 index 000000000..090565310 --- /dev/null +++ b/src/core/hle/kernel/physical_memory.h @@ -0,0 +1,19 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/alignment.h" + +namespace Kernel { + +// This encapsulation serves 2 purposes: +// - First, to encapsulate host physical memory under a single type and set an +// standard for managing it. +// - Second to ensure all host backing memory used is aligned to 256 bytes due +// to strict alignment restrictions on GPU memory. + +using PhysicalMemory = std::vector<u8, Common::AlignmentAllocator<u8, 256>>; + +} // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 2b81a8d4f..e80a12ac3 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <bitset> #include <memory> #include <random> #include "common/alignment.h" @@ -48,7 +49,58 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) { } } // Anonymous namespace -SharedPtr<Process> Process::Create(Core::System& system, std::string name) { +// Represents a page used for thread-local storage. +// +// Each TLS page contains slots that may be used by processes and threads. +// Every process and thread is created with a slot in some arbitrary page +// (whichever page happens to have an available slot). +class TLSPage { +public: + static constexpr std::size_t num_slot_entries = Memory::PAGE_SIZE / Memory::TLS_ENTRY_SIZE; + + explicit TLSPage(VAddr address) : base_address{address} {} + + bool HasAvailableSlots() const { + return !is_slot_used.all(); + } + + VAddr GetBaseAddress() const { + return base_address; + } + + std::optional<VAddr> ReserveSlot() { + for (std::size_t i = 0; i < is_slot_used.size(); i++) { + if (is_slot_used[i]) { + continue; + } + + is_slot_used[i] = true; + return base_address + (i * Memory::TLS_ENTRY_SIZE); + } + + return std::nullopt; + } + + void ReleaseSlot(VAddr address) { + // Ensure that all given addresses are consistent with how TLS pages + // are intended to be used when releasing slots. + ASSERT(IsWithinPage(address)); + ASSERT((address % Memory::TLS_ENTRY_SIZE) == 0); + + const std::size_t index = (address - base_address) / Memory::TLS_ENTRY_SIZE; + is_slot_used[index] = false; + } + +private: + bool IsWithinPage(VAddr address) const { + return base_address <= address && address < base_address + Memory::PAGE_SIZE; + } + + VAddr base_address; + std::bitset<num_slot_entries> is_slot_used; +}; + +SharedPtr<Process> Process::Create(Core::System& system, std::string name, ProcessType type) { auto& kernel = system.Kernel(); SharedPtr<Process> process(new Process(system)); @@ -56,7 +108,8 @@ SharedPtr<Process> Process::Create(Core::System& system, std::string name) { process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; process->program_id = 0; - process->process_id = kernel.CreateNewProcessID(); + process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() + : kernel.CreateNewUserProcessID(); process->capabilities.InitializeForMetadatalessProcess(); std::mt19937 rng(Settings::values.rng_seed.value_or(0)); @@ -76,20 +129,17 @@ u64 Process::GetTotalPhysicalMemoryAvailable() const { return vm_manager.GetTotalPhysicalMemoryAvailable(); } -u64 Process::GetTotalPhysicalMemoryAvailableWithoutMmHeap() const { - // TODO: Subtract the personal heap size from this when the - // personal heap is implemented. - return GetTotalPhysicalMemoryAvailable(); +u64 Process::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const { + return GetTotalPhysicalMemoryAvailable() - GetSystemResourceSize(); } u64 Process::GetTotalPhysicalMemoryUsed() const { - return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size; + return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size + + GetSystemResourceUsage(); } -u64 Process::GetTotalPhysicalMemoryUsedWithoutMmHeap() const { - // TODO: Subtract the personal heap size from this when the - // personal heap is implemented. - return GetTotalPhysicalMemoryUsed(); +u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const { + return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage(); } void Process::RegisterThread(const Thread* thread) { @@ -119,6 +169,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()); @@ -133,19 +184,11 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { } void Process::Run(s32 main_thread_priority, u64 stack_size) { - // The kernel always ensures that the given stack size is page aligned. - main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE); - - // Allocate and map the main thread stack - // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part - // of the user address space. - const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size; - vm_manager - .MapMemoryBlock(mapping_address, std::make_shared<std::vector<u8>>(main_thread_stack_size), - 0, main_thread_stack_size, MemoryState::Stack) - .Unwrap(); + AllocateMainThreadStack(stack_size); + tls_region_address = CreateTLSRegion(); vm_manager.LogLayout(); + ChangeStatus(ProcessStatus::Running); SetupMainThread(*this, kernel, main_thread_priority); @@ -175,69 +218,66 @@ void Process::PrepareForTermination() { stop_threads(system.Scheduler(2).GetThreadList()); stop_threads(system.Scheduler(3).GetThreadList()); + FreeTLSRegion(tls_region_address); + tls_region_address = 0; + ChangeStatus(ProcessStatus::Exited); } /** - * Finds a free location for the TLS section of a thread. - * @param tls_slots The TLS page array of the thread's owner process. - * Returns a tuple of (page, slot, alloc_needed) where: - * page: The index of the first allocated TLS page that has free slots. - * slot: The index of the first free slot in the indicated page. - * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full). + * Attempts to find a TLS page that contains a free slot for + * use by a thread. + * + * @returns If a page with an available slot is found, then an iterator + * pointing to the page is returned. Otherwise the end iterator + * is returned instead. */ -static std::tuple<std::size_t, std::size_t, bool> FindFreeThreadLocalSlot( - const std::vector<std::bitset<8>>& tls_slots) { - // Iterate over all the allocated pages, and try to find one where not all slots are used. - for (std::size_t page = 0; page < tls_slots.size(); ++page) { - const auto& page_tls_slots = tls_slots[page]; - if (!page_tls_slots.all()) { - // We found a page with at least one free slot, find which slot it is - for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) { - if (!page_tls_slots.test(slot)) { - return std::make_tuple(page, slot, false); - } - } - } - } - - return std::make_tuple(0, 0, true); +static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) { + return std::find_if(tls_pages.begin(), tls_pages.end(), + [](const auto& page) { return page.HasAvailableSlots(); }); } -VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) { - auto [available_page, available_slot, needs_allocation] = FindFreeThreadLocalSlot(tls_slots); - const VAddr tls_begin = vm_manager.GetTLSIORegionBaseAddress(); +VAddr Process::CreateTLSRegion() { + auto tls_page_iter = FindTLSPageWithAvailableSlots(tls_pages); - if (needs_allocation) { - tls_slots.emplace_back(0); // The page is completely available at the start - available_page = tls_slots.size() - 1; - available_slot = 0; // Use the first slot in the new page + if (tls_page_iter == tls_pages.cend()) { + const auto region_address = + vm_manager.FindFreeRegion(vm_manager.GetTLSIORegionBaseAddress(), + vm_manager.GetTLSIORegionEndAddress(), Memory::PAGE_SIZE); + ASSERT(region_address.Succeeded()); - // Allocate some memory from the end of the linear heap for this region. - auto& tls_memory = thread.GetTLSMemory(); - tls_memory->insert(tls_memory->end(), Memory::PAGE_SIZE, 0); + const auto map_result = vm_manager.MapMemoryBlock( + *region_address, std::make_shared<PhysicalMemory>(Memory::PAGE_SIZE), 0, + Memory::PAGE_SIZE, MemoryState::ThreadLocal); + ASSERT(map_result.Succeeded()); - vm_manager.RefreshMemoryBlockMappings(tls_memory.get()); + tls_pages.emplace_back(*region_address); - vm_manager.MapMemoryBlock(tls_begin + available_page * Memory::PAGE_SIZE, tls_memory, 0, - Memory::PAGE_SIZE, MemoryState::ThreadLocal); - } + const auto reserve_result = tls_pages.back().ReserveSlot(); + ASSERT(reserve_result.has_value()); - tls_slots[available_page].set(available_slot); + return *reserve_result; + } - return tls_begin + available_page * Memory::PAGE_SIZE + available_slot * Memory::TLS_ENTRY_SIZE; + return *tls_page_iter->ReserveSlot(); } -void Process::FreeTLSSlot(VAddr tls_address) { - const VAddr tls_base = tls_address - vm_manager.GetTLSIORegionBaseAddress(); - const VAddr tls_page = tls_base / Memory::PAGE_SIZE; - const VAddr tls_slot = (tls_base % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; +void Process::FreeTLSRegion(VAddr tls_address) { + const VAddr aligned_address = Common::AlignDown(tls_address, Memory::PAGE_SIZE); + auto iter = + std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) { + return page.GetBaseAddress() == aligned_address; + }); - tls_slots[tls_page].reset(tls_slot); + // Something has gone very wrong if we're freeing a region + // with no actual page available. + ASSERT(iter != tls_pages.cend()); + + iter->ReleaseSlot(tls_address); } void Process::LoadModule(CodeSet module_, VAddr base_addr) { - const auto memory = std::make_shared<std::vector<u8>>(std::move(module_.memory)); + const auto memory = std::make_shared<PhysicalMemory>(std::move(module_.memory)); const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { @@ -280,4 +320,16 @@ void Process::ChangeStatus(ProcessStatus new_status) { WakeupAllWaitingThreads(); } +void Process::AllocateMainThreadStack(u64 stack_size) { + // The kernel always ensures that the given stack size is page aligned. + main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE); + + // Allocate and map the main thread stack + const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size; + vm_manager + .MapMemoryBlock(mapping_address, std::make_shared<PhysicalMemory>(main_thread_stack_size), + 0, main_thread_stack_size, MemoryState::Stack) + .Unwrap(); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 29e016983..c2df451f3 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -5,7 +5,6 @@ #pragma once #include <array> -#include <bitset> #include <cstddef> #include <list> #include <string> @@ -32,6 +31,7 @@ namespace Kernel { class KernelCore; class ResourceLimit; class Thread; +class TLSPage; struct CodeSet; @@ -73,9 +73,15 @@ public: ProcessIDMax = 0xFFFFFFFFFFFFFFFF, }; + // Used to determine how process IDs are assigned. + enum class ProcessType { + KernelInternal, + Userland, + }; + static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; - static SharedPtr<Process> Create(Core::System& system, std::string name); + static SharedPtr<Process> Create(Core::System& system, std::string name, ProcessType type); std::string GetTypeName() const override { return "Process"; @@ -129,6 +135,11 @@ public: return mutex; } + /// Gets the address to the process' dedicated TLS region. + VAddr GetTLSRegionAddress() const { + return tls_region_address; + } + /// Gets the current status of the process ProcessStatus GetStatus() const { return status; @@ -162,8 +173,24 @@ 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; + } + + /// Gets the amount of secure memory currently in use for memory management. + u32 GetSystemResourceUsage() const { + // 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? Games may retrieve this value via + // an SDK function that gets used + available system resource size for debug + // or diagnostic purposes. However, it seems unlikely that a game would make + // decisions based on how much system memory is dedicated to its page tables. + // Is returning a value other than zero wise? + return 0; } /// Whether this process is an AArch64 or AArch32 process. @@ -190,15 +217,15 @@ public: u64 GetTotalPhysicalMemoryAvailable() const; /// Retrieves the total physical memory available to this process in bytes, - /// without the size of the personal heap added to it. - u64 GetTotalPhysicalMemoryAvailableWithoutMmHeap() const; + /// without the size of the personal system resource heap added to it. + u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource() const; /// Retrieves the total physical memory used by this process in bytes. u64 GetTotalPhysicalMemoryUsed() const; /// Retrieves the total physical memory used by this process in bytes, - /// without the size of the personal heap added to it. - u64 GetTotalPhysicalMemoryUsedWithoutMmHeap() const; + /// without the size of the personal system resource heap added to it. + u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const; /// Gets the list of all threads created with this process as their owner. const std::list<const Thread*>& GetThreadList() const { @@ -254,10 +281,10 @@ public: // Thread-local storage management // Marks the next available region as used and returns the address of the slot. - VAddr MarkNextAvailableTLSSlotAsUsed(Thread& thread); + [[nodiscard]] VAddr CreateTLSRegion(); // Frees a used TLS slot identified by the given address - void FreeTLSSlot(VAddr tls_address); + void FreeTLSRegion(VAddr tls_address); private: explicit Process(Core::System& system); @@ -274,6 +301,9 @@ private: /// a process signal. void ChangeStatus(ProcessStatus new_status); + /// Allocates the main thread stack for the process, given the stack size in bytes. + void AllocateMainThreadStack(u64 stack_size); + /// Memory manager for this process. Kernel::VMManager vm_manager; @@ -284,7 +314,7 @@ private: u64 code_memory_size = 0; /// Current status of the process - ProcessStatus status; + ProcessStatus status{}; /// The ID of this process u64 process_id = 0; @@ -292,19 +322,23 @@ 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 /// holds the TLS for a specific thread. This vector contains which parts are in use for each /// page as a bitmask. /// This vector will grow as more pages are allocated for new threads. - std::vector<std::bitset<8>> tls_slots; + std::vector<TLSPage> tls_pages; /// Contains the parsed process capability descriptors. ProcessCapabilities capabilities; @@ -332,8 +366,11 @@ private: /// variable related facilities. Mutex mutex; + /// Address indicating the location of the process' dedicated TLS region. + VAddr tls_region_address = 0; + /// Random values for svcGetInfo RandomEntropy - std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy; + std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{}; /// List of threads that are running with this process as their owner. std::list<const Thread*> thread_list; diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index f15c5ee36..a815c4eea 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -28,7 +28,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_ shared_memory->other_permissions = other_permissions; if (address == 0) { - shared_memory->backing_block = std::make_shared<std::vector<u8>>(size); + shared_memory->backing_block = std::make_shared<Kernel::PhysicalMemory>(size); shared_memory->backing_block_offset = 0; // Refresh the address mappings for the current process. @@ -59,8 +59,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_ } SharedPtr<SharedMemory> SharedMemory::CreateForApplet( - KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, std::size_t offset, u64 size, - MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { + KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset, + u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel)); shared_memory->owner_process = nullptr; diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index c2b6155e1..01ca6dcd2 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/physical_memory.h" #include "core/hle/kernel/process.h" #include "core/hle/result.h" @@ -62,12 +63,10 @@ public: * block. * @param name Optional object name, used for debugging purposes. */ - static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel, - std::shared_ptr<std::vector<u8>> heap_block, - std::size_t offset, u64 size, - MemoryPermission permissions, - MemoryPermission other_permissions, - std::string name = "Unknown Applet"); + static SharedPtr<SharedMemory> CreateForApplet( + KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset, + u64 size, MemoryPermission permissions, MemoryPermission other_permissions, + std::string name = "Unknown Applet"); std::string GetTypeName() const override { return "SharedMemory"; @@ -135,7 +134,7 @@ private: ~SharedMemory() override; /// Backing memory for this shared memory block. - std::shared_ptr<std::vector<u8>> backing_block; + std::shared_ptr<PhysicalMemory> backing_block; /// Offset into the backing block for this shared memory. std::size_t backing_block_offset = 0; /// Size of the memory block. Page-aligned. diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index f9c606bc5..1fd1a732a 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -38,6 +38,7 @@ #include "core/hle/result.h" #include "core/hle/service/service.h" #include "core/memory.h" +#include "core/reporter.h" namespace Kernel { namespace { @@ -97,9 +98,9 @@ ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_add return ERR_INVALID_ADDRESS_STATE; } - if (!vm_manager.IsWithinNewMapRegion(dst_addr, size)) { + if (!vm_manager.IsWithinStackRegion(dst_addr, size)) { LOG_ERROR(Kernel_SVC, - "Destination is not within the new map region, addr=0x{:016X}, size=0x{:016X}", + "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}", dst_addr, size); return ERR_INVALID_MEMORY_RANGE; } @@ -317,7 +318,14 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad return result; } - return vm_manager.UnmapRange(dst_addr, size); + const auto unmap_res = vm_manager.UnmapRange(dst_addr, size); + + // Reprotect the source mapping on success + if (unmap_res.IsSuccess()) { + ASSERT(vm_manager.ReprotectRange(src_addr, size, VMAPermission::ReadWrite).IsSuccess()); + } + + return unmap_res; } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -594,6 +602,7 @@ struct BreakReason { static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { BreakReason break_reason{reason}; bool has_dumped_buffer{}; + std::vector<u8> debug_buffer; const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { if (sz == 0 || addr == 0 || has_dumped_buffer) { @@ -605,7 +614,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", Memory::Read32(addr)); } else { // We don't know what's in here so we'll hexdump it - std::vector<u8> debug_buffer(sz); + debug_buffer.resize(sz); Memory::ReadBlock(addr, debug_buffer.data(), sz); std::string hexdump; for (std::size_t i = 0; i < debug_buffer.size(); i++) { @@ -664,6 +673,10 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { break; } + system.GetReporter().SaveSvcBreakReport( + static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger, info1, + info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); + if (!break_reason.signal_debugger) { LOG_CRITICAL( Debug_Emulated, @@ -720,19 +733,19 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha // 2.0.0+ ASLRRegionBaseAddr = 12, ASLRRegionSize = 13, - NewMapRegionBaseAddr = 14, - NewMapRegionSize = 15, + StackRegionBaseAddr = 14, + StackRegionSize = 15, // 3.0.0+ - IsVirtualAddressMemoryEnabled = 16, - PersonalMmHeapUsage = 17, + SystemResourceSize = 16, + SystemResourceUsage = 17, TitleId = 18, // 4.0.0+ PrivilegedProcessId = 19, // 5.0.0+ UserExceptionContextAddr = 20, // 6.0.0+ - TotalPhysicalMemoryAvailableWithoutMmHeap = 21, - TotalPhysicalMemoryUsedWithoutMmHeap = 22, + TotalPhysicalMemoryAvailableWithoutSystemResource = 21, + TotalPhysicalMemoryUsedWithoutSystemResource = 22, }; const auto info_id_type = static_cast<GetInfoType>(info_id); @@ -746,16 +759,16 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha case GetInfoType::HeapRegionSize: case GetInfoType::ASLRRegionBaseAddr: case GetInfoType::ASLRRegionSize: - case GetInfoType::NewMapRegionBaseAddr: - case GetInfoType::NewMapRegionSize: + case GetInfoType::StackRegionBaseAddr: + 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: - case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: { + case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: + case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: { if (info_sub_id != 0) { return ERR_INVALID_ENUM_VALUE; } @@ -800,12 +813,12 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha *result = process->VMManager().GetASLRRegionSize(); return RESULT_SUCCESS; - case GetInfoType::NewMapRegionBaseAddr: - *result = process->VMManager().GetNewMapRegionBaseAddress(); + case GetInfoType::StackRegionBaseAddr: + *result = process->VMManager().GetStackRegionBaseAddress(); return RESULT_SUCCESS; - case GetInfoType::NewMapRegionSize: - *result = process->VMManager().GetNewMapRegionSize(); + case GetInfoType::StackRegionSize: + *result = process->VMManager().GetStackRegionSize(); return RESULT_SUCCESS; case GetInfoType::TotalPhysicalMemoryAvailable: @@ -816,8 +829,13 @@ 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: + LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); + *result = process->GetSystemResourceUsage(); return RESULT_SUCCESS; case GetInfoType::TitleId: @@ -825,17 +843,15 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha return RESULT_SUCCESS; case GetInfoType::UserExceptionContextAddr: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query user exception context address, returned 0"); - *result = 0; + *result = process->GetTLSRegionAddress(); return RESULT_SUCCESS; - case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap: - *result = process->GetTotalPhysicalMemoryAvailable(); + case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: + *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); return RESULT_SUCCESS; - case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: - *result = process->GetTotalPhysicalMemoryUsedWithoutMmHeap(); + case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: + *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); return RESULT_SUCCESS; default: @@ -940,6 +956,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; + } + + Process* const current_process = system.Kernel().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; + } + + Process* const current_process = system.Kernel().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); @@ -1641,8 +1737,8 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var // Wait for an address (via Address Arbiter) static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, s32 value, s64 timeout) { - LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", - address, type, value, timeout); + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", address, + type, value, timeout); // If the passed address is a kernel virtual address, return invalid memory state. if (Memory::IsKernelVirtualAddress(address)) { @@ -1664,8 +1760,8 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, // Signals to an address (via Address Arbiter) static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value, s32 num_to_wake) { - LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", - address, type, value, num_to_wake); + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", + address, type, value, num_to_wake); // If the passed address is a kernel virtual address, return invalid memory state. if (Memory::IsKernelVirtualAddress(address)) { @@ -2297,8 +2393,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"}, diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 865473c6f..c2d8d0dc3 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -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); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index c73a40977..ec529e7f2 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -65,7 +65,7 @@ void Thread::Stop() { owner_process->UnregisterThread(this); // Mark the TLS slot in the thread's page as free. - owner_process->FreeTLSSlot(tls_address); + owner_process->FreeTLSRegion(tls_address); } void Thread::WakeAfterDelay(s64 nanoseconds) { @@ -76,13 +76,13 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { // This function might be called from any thread so we have to be cautious and use the // thread-safe version of ScheduleEvent. const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds}); - Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe( + Core::System::GetInstance().CoreTiming().ScheduleEvent( cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle); } void Thread::CancelWakeupTimer() { - Core::System::GetInstance().CoreTiming().UnscheduleEventThreadsafe( - kernel.ThreadWakeupCallbackEventType(), callback_handle); + Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), + callback_handle); } static std::optional<s32> GetNextProcessorId(u64 mask) { @@ -205,9 +205,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name thread->name = std::move(name); thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); thread->owner_process = &owner_process; + thread->tls_address = thread->owner_process->CreateTLSRegion(); thread->scheduler = &system.Scheduler(processor_id); thread->scheduler->AddThread(thread); - thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); thread->owner_process->RegisterThread(thread.get()); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index b4b9cda7c..07e989637 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -5,7 +5,6 @@ #pragma once #include <functional> -#include <memory> #include <string> #include <vector> @@ -78,9 +77,6 @@ enum class ThreadActivity : u32 { class Thread final : public WaitObject { public: - using TLSMemory = std::vector<u8>; - using TLSMemoryPtr = std::shared_ptr<TLSMemory>; - using MutexWaitingThreads = std::vector<SharedPtr<Thread>>; using ThreadContext = Core::ARM_Interface::ThreadContext; @@ -169,14 +165,6 @@ public: return thread_id; } - TLSMemoryPtr& GetTLSMemory() { - return tls_memory; - } - - const TLSMemoryPtr& GetTLSMemory() const { - return tls_memory; - } - /// Resumes a thread from waiting void ResumeFromWait(); @@ -463,11 +451,9 @@ private: u32 ideal_core{0xFFFFFFFF}; u64 affinity_mask{0x1}; - TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>(); + ThreadActivity activity = ThreadActivity::Normal; std::string name; - - ThreadActivity activity = ThreadActivity::Normal; }; /** diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp index 26c4e5e67..1113c815e 100644 --- a/src/core/hle/kernel/transfer_memory.cpp +++ b/src/core/hle/kernel/transfer_memory.cpp @@ -47,7 +47,7 @@ ResultCode TransferMemory::MapMemory(VAddr address, u64 size, MemoryPermission p return ERR_INVALID_STATE; } - backing_block = std::make_shared<std::vector<u8>>(size); + backing_block = std::make_shared<PhysicalMemory>(size); const auto map_state = owner_permissions == MemoryPermission::None ? MemoryState::TransferMemoryIsolated diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h index a140b1e2b..6be9dc094 100644 --- a/src/core/hle/kernel/transfer_memory.h +++ b/src/core/hle/kernel/transfer_memory.h @@ -8,6 +8,7 @@ #include <vector> #include "core/hle/kernel/object.h" +#include "core/hle/kernel/physical_memory.h" union ResultCode; @@ -82,7 +83,7 @@ private: ~TransferMemory() override; /// Memory block backing this instance. - std::shared_ptr<std::vector<u8>> backing_block; + std::shared_ptr<PhysicalMemory> backing_block; /// The base address for the memory managed by this instance. VAddr base_address = 0; diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 6d6980aba..c7af87073 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -5,13 +5,15 @@ #include <algorithm> #include <iterator> #include <utility> +#include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/memory_hook.h" -#include "core/arm/arm_interface.h" #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,10 +51,14 @@ 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)) { + if ((attribute & MemoryAttribute::DeviceMapped) == MemoryAttribute::DeviceMapped) { + // TODO: Can device mapped memory be merged sanely? + // Not merging it may cause inaccuracies versus hardware when memory layout is queried. return false; } + if (type == VMAType::AllocatedMemoryBlock) { + return true; + } if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) { return false; } @@ -68,9 +74,7 @@ VMManager::VMManager(Core::System& system) : system{system} { Reset(FileSys::ProgramAddressSpaceType::Is39Bit); } -VMManager::~VMManager() { - Reset(FileSys::ProgramAddressSpaceType::Is39Bit); -} +VMManager::~VMManager() = default; void VMManager::Reset(FileSys::ProgramAddressSpaceType type) { Clear(); @@ -100,9 +104,9 @@ bool VMManager::IsValidHandle(VMAHandle handle) const { } ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, - std::shared_ptr<std::vector<u8>> block, + std::shared_ptr<PhysicalMemory> block, std::size_t offset, u64 size, - MemoryState state) { + MemoryState state, VMAPermission perm) { ASSERT(block != nullptr); ASSERT(offset + size <= block->size()); @@ -111,17 +115,8 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, VirtualMemoryArea& final_vma = vma_handle->second; ASSERT(final_vma.size == size); - system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset, - 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; @@ -139,11 +134,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me VirtualMemoryArea& final_vma = vma_handle->second; ASSERT(final_vma.size == size); - system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - final_vma.type = VMAType::BackingMemory; final_vma.permissions = VMAPermission::ReadWrite; final_vma.state = state; @@ -154,22 +144,33 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me } ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const { - // Find the first Free VMA. - const VAddr base = GetASLRRegionBaseAddress(); - const VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) { - if (vma.second.type != VMAType::Free) - return false; + return FindFreeRegion(GetASLRRegionBaseAddress(), GetASLRRegionEndAddress(), size); +} - const VAddr vma_end = vma.second.base + vma.second.size; - return vma_end > base && vma_end >= base + size; - }); +ResultVal<VAddr> VMManager::FindFreeRegion(VAddr begin, VAddr end, u64 size) const { + ASSERT(begin < end); + ASSERT(size <= end - begin); - if (vma_handle == vma_map.end()) { + const VMAHandle vma_handle = + std::find_if(vma_map.begin(), vma_map.end(), [begin, end, size](const auto& vma) { + if (vma.second.type != VMAType::Free) { + return false; + } + const VAddr vma_base = vma.second.base; + const VAddr vma_end = vma_base + vma.second.size; + const VAddr assumed_base = (begin < vma_base) ? vma_base : begin; + const VAddr used_range = assumed_base + size; + + return vma_base <= assumed_base && assumed_base < used_range && used_range < end && + used_range <= vma_end; + }); + + if (vma_handle == vma_map.cend()) { // TODO(Subv): Find the correct error code here. return ResultCode(-1); } - const VAddr target = std::max(base, vma_handle->second.base); + const VAddr target = std::max(begin, vma_handle->second.base); return MakeResult<VAddr>(target); } @@ -221,11 +222,6 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) { ASSERT(FindVMA(target)->second.size >= size); - system.ArmInterface(0).UnmapMemory(target, size); - system.ArmInterface(1).UnmapMemory(target, size); - system.ArmInterface(2).UnmapMemory(target, size); - system.ArmInterface(3).UnmapMemory(target, size); - return RESULT_SUCCESS; } @@ -265,7 +261,7 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) { if (heap_memory == nullptr) { // Initialize heap - heap_memory = std::make_shared<std::vector<u8>>(size); + heap_memory = std::make_shared<PhysicalMemory>(size); heap_end = heap_region_base + size; } else { UnmapRange(heap_region_base, GetCurrentHeapSize()); @@ -299,6 +295,162 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) { return MakeResult<VAddr>(heap_region_base); } +ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { + // Check how much memory we've already mapped. + const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); + if (mapped_size_result.Failed()) { + return mapped_size_result.Code(); + } + + // If we've already mapped the desired amount, return early. + const std::size_t mapped_size = *mapped_size_result; + if (mapped_size == size) { + return RESULT_SUCCESS; + } + + // Check that we can map the memory we want. + const auto res_limit = system.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; + ResultCode result = RESULT_SUCCESS; + + // Iterate, trying to map memory. + { + const auto end_addr = target + size; + const auto last_addr = end_addr - 1; + VAddr cur_addr = target; + + auto iter = FindVMA(target); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const auto vma_start = vma.base; + const auto vma_end = vma_start + vma.size; + const auto vma_last = vma_end - 1; + + // Map the memory block + const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); + if (vma.state == MemoryState::Unmapped) { + const auto map_res = + MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0, + map_size, MemoryState::Heap, VMAPermission::ReadWrite); + result = map_res.Code(); + if (result.IsError()) { + break; + } + + mapped_regions.emplace_back(cur_addr, map_size); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + } + } + + // If we failed, unmap memory. + if (result.IsError()) { + for (const auto [unmap_address, unmap_size] : mapped_regions) { + ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), + "Failed to unmap memory range."); + } + + return result; + } + + // Update amount of mapped physical memory. + physical_memory_mapped += size - mapped_size; + + return RESULT_SUCCESS; +} + +ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { + // Check how much memory is currently mapped. + const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); + if (mapped_size_result.Failed()) { + return mapped_size_result.Code(); + } + + // If we've already unmapped all the memory, return early. + const std::size_t mapped_size = *mapped_size_result; + if (mapped_size == 0) { + return RESULT_SUCCESS; + } + + // Keep track of the memory regions we unmap. + std::vector<std::pair<u64, u64>> unmapped_regions; + ResultCode result = RESULT_SUCCESS; + + // Try to unmap regions. + { + const auto end_addr = target + size; + const auto last_addr = end_addr - 1; + VAddr cur_addr = target; + + auto iter = FindVMA(target); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const auto vma_start = vma.base; + const auto vma_end = vma_start + vma.size; + const auto vma_last = vma_end - 1; + + // Unmap the memory block + const auto unmap_size = std::min(end_addr - cur_addr, vma_end - cur_addr); + if (vma.state == MemoryState::Heap) { + result = UnmapRange(cur_addr, unmap_size); + if (result.IsError()) { + break; + } + + unmapped_regions.emplace_back(cur_addr, unmap_size); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + } + } + + // If we failed, re-map regions. + // TODO: Preserve memory contents? + if (result.IsError()) { + for (const auto [map_address, map_size] : unmapped_regions) { + const auto remap_res = + MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size, + MemoryState::Heap, VMAPermission::None); + ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block."); + } + + return result; + } + + // Update mapped amount + physical_memory_mapped -= mapped_size; + + 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( @@ -438,7 +590,7 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem ASSERT_MSG(vma_offset + size <= vma->second.size, "Shared memory exceeds bounds of mapped block"); - const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block; + const std::shared_ptr<PhysicalMemory>& backing_block = vma->second.backing_block; const std::size_t backing_block_offset = vma->second.offset + vma_offset; CASCADE_RESULT(auto new_vma, @@ -446,12 +598,12 @@ 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; } -void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) { +void VMManager::RefreshMemoryBlockMappings(const PhysicalMemory* block) { // If this ever proves to have a noticeable performance impact, allow users of the function to // specify a specific range of addresses to limit the scan to. for (const auto& p : vma_map) { @@ -579,14 +731,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; } @@ -595,6 +747,44 @@ 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)) { + const auto right_begin = right.backing_block->begin() + right.offset; + const auto right_end = right_begin + right.size; + + // 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_begin, right_end); + } else { + // Slow case: make a new memory block for left and right. + const auto left_begin = left.backing_block->begin() + left.offset; + const auto left_end = left_begin + left.size; + const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end)); + const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end)); + + auto new_memory = std::make_shared<PhysicalMemory>(); + new_memory->reserve(left_size + right_size); + new_memory->insert(new_memory->end(), left_begin, left_end); + new_memory->insert(new_memory->end(), right_begin, right_end); + + left.backing_block = std::move(new_memory); + left.offset = 0; + } + + // Page table update is needed, because backing memory changed. + left.size += right.size; + UpdatePageTableForVMA(left); + } else { + // Just update the size. + left.size += right.size; + } +} + void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { switch (vma.type) { case VMAType::Free: @@ -616,9 +806,11 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType type) { u64 map_region_size = 0; u64 heap_region_size = 0; - u64 new_map_region_size = 0; + u64 stack_region_size = 0; u64 tls_io_region_size = 0; + u64 stack_and_tls_io_end = 0; + switch (type) { case FileSys::ProgramAddressSpaceType::Is32Bit: case FileSys::ProgramAddressSpaceType::Is32BitNoMap: @@ -634,6 +826,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty map_region_size = 0; heap_region_size = 0x80000000; } + stack_and_tls_io_end = 0x40000000; break; case FileSys::ProgramAddressSpaceType::Is36Bit: address_space_width = 36; @@ -643,6 +836,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty aslr_region_end = aslr_region_base + 0xFF8000000; map_region_size = 0x180000000; heap_region_size = 0x180000000; + stack_and_tls_io_end = 0x80000000; break; case FileSys::ProgramAddressSpaceType::Is39Bit: address_space_width = 39; @@ -652,7 +846,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty aslr_region_end = aslr_region_base + 0x7FF8000000; map_region_size = 0x1000000000; heap_region_size = 0x180000000; - new_map_region_size = 0x80000000; + stack_region_size = 0x80000000; tls_io_region_size = 0x1000000000; break; default: @@ -660,6 +854,8 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty return; } + const u64 stack_and_tls_io_begin = aslr_region_base; + address_space_base = 0; address_space_end = 1ULL << address_space_width; @@ -670,15 +866,20 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty heap_region_end = heap_region_base + heap_region_size; heap_end = heap_region_base; - new_map_region_base = heap_region_end; - new_map_region_end = new_map_region_base + new_map_region_size; + stack_region_base = heap_region_end; + stack_region_end = stack_region_base + stack_region_size; - tls_io_region_base = new_map_region_end; + tls_io_region_base = stack_region_end; tls_io_region_end = tls_io_region_base + tls_io_region_size; - if (new_map_region_size == 0) { - new_map_region_base = address_space_base; - new_map_region_end = address_space_end; + if (stack_region_size == 0) { + stack_region_base = stack_and_tls_io_begin; + stack_region_end = stack_and_tls_io_end; + } + + if (tls_io_region_size == 0) { + tls_io_region_base = stack_and_tls_io_begin; + tls_io_region_end = stack_and_tls_io_end; } } @@ -758,6 +959,84 @@ VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, Memo std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask)); } +ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address, + std::size_t size) const { + const VAddr end_addr = address + size; + const VAddr last_addr = end_addr - 1; + std::size_t mapped_size = 0; + + VAddr cur_addr = address; + auto iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const VAddr vma_start = vma.base; + const VAddr vma_end = vma_start + vma.size; + const VAddr vma_last = vma_end - 1; + + // Add size if relevant. + if (vma.state != MemoryState::Unmapped) { + mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = std::next(iter); + ASSERT(iter != vma_map.end()); + } + + return MakeResult(mapped_size); +} + +ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr address, + std::size_t size) const { + const VAddr end_addr = address + size; + const VAddr last_addr = end_addr - 1; + std::size_t mapped_size = 0; + + VAddr cur_addr = address; + auto iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const auto vma_start = vma.base; + const auto vma_end = vma_start + vma.size; + const auto vma_last = vma_end - 1; + const auto state = vma.state; + const auto attr = vma.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; + } + + // Add size if relevant. + if (state != MemoryState::Unmapped) { + mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = std::next(iter); + ASSERT(iter != vma_map.end()); + } + + return MakeResult(mapped_size); +} + u64 VMManager::GetTotalPhysicalMemoryAvailable() const { LOG_WARNING(Kernel, "(STUBBED) called"); return 0xF8000000; @@ -870,21 +1149,21 @@ bool VMManager::IsWithinMapRegion(VAddr address, u64 size) const { return IsInsideAddressRange(address, size, GetMapRegionBaseAddress(), GetMapRegionEndAddress()); } -VAddr VMManager::GetNewMapRegionBaseAddress() const { - return new_map_region_base; +VAddr VMManager::GetStackRegionBaseAddress() const { + return stack_region_base; } -VAddr VMManager::GetNewMapRegionEndAddress() const { - return new_map_region_end; +VAddr VMManager::GetStackRegionEndAddress() const { + return stack_region_end; } -u64 VMManager::GetNewMapRegionSize() const { - return new_map_region_end - new_map_region_base; +u64 VMManager::GetStackRegionSize() const { + return stack_region_end - stack_region_base; } -bool VMManager::IsWithinNewMapRegion(VAddr address, u64 size) const { - return IsInsideAddressRange(address, size, GetNewMapRegionBaseAddress(), - GetNewMapRegionEndAddress()); +bool VMManager::IsWithinStackRegion(VAddr address, u64 size) const { + return IsInsideAddressRange(address, size, GetStackRegionBaseAddress(), + GetStackRegionEndAddress()); } VAddr VMManager::GetTLSIORegionBaseAddress() const { diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index dfbf7a894..850a7ebc3 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "common/memory_hook.h" #include "common/page_table.h" +#include "core/hle/kernel/physical_memory.h" #include "core/hle/result.h" #include "core/memory.h" @@ -290,7 +291,7 @@ struct VirtualMemoryArea { // Settings for type = AllocatedMemoryBlock /// Memory block backing this VMA. - std::shared_ptr<std::vector<u8>> backing_block = nullptr; + std::shared_ptr<PhysicalMemory> backing_block = nullptr; /// Offset into the backing_memory the mapping starts from. std::size_t offset = 0; @@ -348,8 +349,9 @@ public: * @param size Size of the mapping. * @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); + ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<PhysicalMemory> block, + std::size_t offset, u64 size, MemoryState state, + VMAPermission perm = VMAPermission::ReadWrite); /** * Maps an unmanaged host memory pointer at a given address. @@ -362,14 +364,39 @@ public: ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state); /** - * Finds the first free address that can hold a region of the desired size. + * Finds the first free memory region of the given size within + * the user-addressable ASLR memory region. * - * @param size Size of the desired region. - * @return The found free address. + * @param size The size of the desired region in bytes. + * + * @returns If successful, the base address of the free region with + * the given size. */ ResultVal<VAddr> FindFreeRegion(u64 size) const; /** + * Finds the first free address range that can hold a region of the desired size + * + * @param begin The starting address of the range. + * This is treated as an inclusive beginning address. + * + * @param end The ending address of the range. + * This is treated as an exclusive ending address. + * + * @param size The size of the free region to attempt to locate, + * in bytes. + * + * @returns If successful, the base address of the free region with + * the given size. + * + * @returns If unsuccessful, a result containing an error code. + * + * @pre The starting address must be less than the ending address. + * @pre The size must not exceed the address range itself. + */ + ResultVal<VAddr> FindFreeRegion(VAddr begin, VAddr end, u64 size) const; + + /** * Maps a memory-mapped IO region at a given address. * * @param target The guest address to start the mapping at. @@ -425,6 +452,34 @@ public: /// ResultVal<VAddr> SetHeapSize(u64 size); + /// Maps memory at a given address. + /// + /// @param target 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 that SystemResourceSize be 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 target 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 that SystemResourceSize be 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. @@ -493,7 +548,7 @@ public: * Scans all VMAs and updates the page table range of any that use the given vector as backing * memory. This should be called after any operation that causes reallocation of the vector. */ - void RefreshMemoryBlockMappings(const std::vector<u8>* block); + void RefreshMemoryBlockMappings(const PhysicalMemory* block); /// Dumps the address space layout to the log, for debugging void LogLayout() const; @@ -571,17 +626,17 @@ public: /// Determines whether or not the specified range is within the map region. bool IsWithinMapRegion(VAddr address, u64 size) const; - /// Gets the base address of the new map region. - VAddr GetNewMapRegionBaseAddress() const; + /// Gets the base address of the stack region. + VAddr GetStackRegionBaseAddress() const; - /// Gets the end address of the new map region. - VAddr GetNewMapRegionEndAddress() const; + /// Gets the end address of the stack region. + VAddr GetStackRegionEndAddress() const; - /// Gets the total size of the new map region in bytes. - u64 GetNewMapRegionSize() const; + /// Gets the total size of the stack region in bytes. + u64 GetStackRegionSize() const; - /// Determines whether or not the given address range is within the new map region - bool IsWithinNewMapRegion(VAddr address, u64 size) const; + /// Determines whether or not the given address range is within the stack region + bool IsWithinStackRegion(VAddr address, u64 size) const; /// Gets the base address of the TLS IO region. VAddr GetTLSIORegionBaseAddress() const; @@ -632,6 +687,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); @@ -676,6 +736,13 @@ private: MemoryAttribute attribute_mask, MemoryAttribute attribute, MemoryAttribute ignore_mask) const; + /// Gets the amount of memory currently mapped (state != Unmapped) in a range. + ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const; + + /// Gets the amount of memory unmappable by UnmapPhysicalMemory in a range. + ResultVal<std::size_t> SizeOfUnmappablePhysicalMemoryInRange(VAddr address, + std::size_t size) const; + /** * A map covering the entirety of the managed address space, keyed by the `base` field of each * VMA. It must always be modified by splitting or merging VMAs, so that the invariant @@ -701,8 +768,8 @@ private: VAddr map_region_base = 0; VAddr map_region_end = 0; - VAddr new_map_region_base = 0; - VAddr new_map_region_end = 0; + VAddr stack_region_base = 0; + VAddr stack_region_end = 0; VAddr tls_io_region_base = 0; VAddr tls_io_region_end = 0; @@ -711,12 +778,17 @@ private: // the entire virtual address space extents that bound the allocations, including any holes. // This makes deallocation and reallocation of holes fast and keeps process memory contiguous // in the emulator address space, allowing Memory::GetPointer to be reasonably safe. - std::shared_ptr<std::vector<u8>> heap_memory; + std::shared_ptr<PhysicalMemory> heap_memory; // The end of the currently allocated heap. This is not an inclusive // 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 |