From 39b22cee8ce97a8396735a23a4de73725fecd696 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Tue, 28 Jul 2020 15:09:07 -0700 Subject: [PATCH] kern: implement KCodeMemory (and SVCs) --- .../arch/arm64/kern_k_process_page_table.hpp | 4 +- .../include/mesosphere/kern_k_code_memory.hpp | 29 ++++- .../mesosphere/kern_k_page_table_base.hpp | 2 +- .../source/kern_k_page_table_base.cpp | 2 +- .../source/kern_k_transfer_memory.cpp | 2 +- .../source/svc/kern_svc_code_memory.cpp | 123 +++++++++++++++++- 6 files changed, 152 insertions(+), 10 deletions(-) diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp index 966f28266..71ed1d3bb 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp @@ -156,8 +156,8 @@ namespace ams::kern::arch::arm64 { return this->page_table.UnlockForTransferMemory(address, size, pg); } - Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { - return this->page_table.LockForCodeMemory(out, address, size, perm); + Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size) { + return this->page_table.LockForCodeMemory(out, address, size); } Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp index 5f3f30afa..29467b701 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp @@ -22,8 +22,35 @@ namespace ams::kern { class KCodeMemory final : public KAutoObjectWithSlabHeapAndContainer { MESOSPHERE_AUTOOBJECT_TRAITS(KCodeMemory, KAutoObject); + private: + TYPED_STORAGE(KPageGroup) page_group; + KProcess *owner; + KProcessAddress address; + KLightLock lock; + bool is_initialized; + bool is_owner_mapped; + bool is_mapped; public: - /* TODO: This is a placeholder definition. */ + explicit KCodeMemory() : owner(nullptr), address(Null), is_initialized(false), is_owner_mapped(false), is_mapped(false) { + /* ... */ + } + + virtual ~KCodeMemory() { /* ... */ } + + Result Initialize(KProcessAddress address, size_t size); + virtual void Finalize() override; + + Result Map(KProcessAddress address, size_t size); + Result Unmap(KProcessAddress address, size_t size); + Result MapToOwner(KProcessAddress address, size_t size, ams::svc::MemoryPermission perm); + Result UnmapFromOwner(KProcessAddress address, size_t size); + + virtual bool IsInitialized() const override { return this->is_initialized; } + static void PostDestroy(uintptr_t arg) { /* ... */ } + + KProcess *GetOwner() const { return this->owner; } + KProcessAddress GetSourceAddress() { return this->address; } + size_t GetSize() const { return this->is_initialized ? GetReference(this->page_group).GetNumPages() * PageSize : 0; } }; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp index c1854f15f..983c852d0 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -331,7 +331,7 @@ namespace ams::kern { Result LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm); Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg); - Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm); + Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size); Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg); Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); diff --git a/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index 3097e7eba..b200a42ef 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -1981,7 +1981,7 @@ namespace ams::kern { KMemoryAttribute_AnyLocked | KMemoryAttribute_Locked, std::addressof(pg)); } - Result KPageTableBase::LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + Result KPageTableBase::LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size) { return this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState_FlagCanCodeMemory, KMemoryState_FlagCanCodeMemory, KMemoryPermission_All, KMemoryPermission_UserReadWrite, diff --git a/libraries/libmesosphere/source/kern_k_transfer_memory.cpp b/libraries/libmesosphere/source/kern_k_transfer_memory.cpp index da9e4086e..39a3e197d 100644 --- a/libraries/libmesosphere/source/kern_k_transfer_memory.cpp +++ b/libraries/libmesosphere/source/kern_k_transfer_memory.cpp @@ -48,7 +48,7 @@ namespace ams::kern { void KTransferMemory::Finalize() { MESOSPHERE_ASSERT_THIS(); - /* Unmap. */ + /* Unlock. */ if (!this->is_mapped) { const size_t size = GetReference(this->page_group).GetNumPages() * PageSize; MESOSPHERE_R_ABORT_UNLESS(this->owner->GetPageTable().UnlockForTransferMemory(this->address, size, GetReference(this->page_group))); diff --git a/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp b/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp index e47135698..f217950da 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp @@ -21,28 +21,143 @@ namespace ams::kern::svc { namespace { + constexpr bool IsValidMapCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_ReadWrite; + } + constexpr bool IsValidMapToOwnerCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_Read || perm == ams::svc::MemoryPermission_ReadExecute; + } + + constexpr bool IsValidUnmapCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_None; + } + + constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_None; + } + + Result CreateCodeMemory(ams::svc::Handle *out, uintptr_t address, size_t size) { + /* Validate address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Create the code memory. */ + KCodeMemory *code_mem = KCodeMemory::Create(); + R_UNLESS(code_mem != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { code_mem->Close(); }; + + /* Verify that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Initialize the code memory. */ + R_TRY(code_mem->Initialize(address, size)); + + /* Register the code memory. */ + R_TRY(KCodeMemory::Register(code_mem)); + + /* Add the code memory to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out, code_mem)); + + return ResultSuccess(); + } + + Result ControlCodeMemory(ams::svc::Handle code_memory_handle, ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + /* Validate the address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(address == static_cast(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast(size), svc::ResultInvalidCurrentMemory()); + + /* Get the code memory from its handle. */ + KScopedAutoObject code_mem = GetCurrentProcess().GetHandleTable().GetObject(code_memory_handle); + R_UNLESS(code_mem.IsNotNull(), svc::ResultInvalidHandle()); + + /* NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. */ + /* This enables homebrew usage of these SVCs for JIT. */ + /* R_UNLESS(code_mem->GetOwner() != GetCurrentProcessPointer(), svc::ResultInvalidHandle()); */ + + /* Perform the operation. */ + switch (operation) { + case ams::svc::CodeMemoryOperation_Map: + { + /* Check that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_CodeOut), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidMapCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Map the memory. */ + R_TRY(code_mem->Map(address, size)); + } + break; + case ams::svc::CodeMemoryOperation_Unmap: + { + /* Check that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_CodeOut), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Unmap the memory. */ + R_TRY(code_mem->Unmap(address, size)); + } + break; + case ams::svc::CodeMemoryOperation_MapToOwner: + { + /* Check that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_GeneratedCode), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Map the memory to its owner. */ + R_TRY(code_mem->MapToOwner(address, size, perm)); + } + break; + case ams::svc::CodeMemoryOperation_UnmapFromOwner: + { + /* Check that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_GeneratedCode), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Unmap the memory from its owner. */ + R_TRY(code_mem->UnmapFromOwner(address, size)); + } + break; + default: + return svc::ResultInvalidEnumValue(); + } + + return ResultSuccess(); + } } /* ============================= 64 ABI ============================= */ Result CreateCodeMemory64(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcCreateCodeMemory64 was called."); + return CreateCodeMemory(out_handle, address, size); } Result ControlCodeMemory64(ams::svc::Handle code_memory_handle, ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { - MESOSPHERE_PANIC("Stubbed SvcControlCodeMemory64 was called."); + return ControlCodeMemory(code_memory_handle, operation, address, size, perm); } /* ============================= 64From32 ABI ============================= */ Result CreateCodeMemory64From32(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size) { - MESOSPHERE_PANIC("Stubbed SvcCreateCodeMemory64From32 was called."); + return CreateCodeMemory(out_handle, address, size); } Result ControlCodeMemory64From32(ams::svc::Handle code_memory_handle, ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { - MESOSPHERE_PANIC("Stubbed SvcControlCodeMemory64From32 was called."); + return ControlCodeMemory(code_memory_handle, operation, address, size, perm); } }