diff --git a/tests/TestSvc/source/test_main.cpp b/tests/TestSvc/source/test_main.cpp index 6ae793ea1..8f34f06d2 100644 --- a/tests/TestSvc/source/test_main.cpp +++ b/tests/TestSvc/source/test_main.cpp @@ -15,12 +15,8 @@ */ #include -#define CATCH_CONFIG_NOSTDOUT #define CATCH_CONFIG_RUNNER -#define CATCH_CONFIG_PREFIX_ALL -#define CATCH_CONFIG_DISABLE_EXCEPTIONS -#define CATCH_CONFIG_NO_POSIX_SIGNALS -#include "catch.hpp" +#include "util_catch.hpp" namespace ams { diff --git a/tests/TestSvc/source/test_set_heap_size.cpp b/tests/TestSvc/source/test_set_heap_size.cpp index 40953265a..36d05d197 100644 --- a/tests/TestSvc/source/test_set_heap_size.cpp +++ b/tests/TestSvc/source/test_set_heap_size.cpp @@ -14,12 +14,8 @@ * along with this program. If not, see . */ #include - -#define CATCH_CONFIG_NOSTDOUT -#define CATCH_CONFIG_PREFIX_ALL -#define CATCH_CONFIG_DISABLE_EXCEPTIONS -#define CATCH_CONFIG_NO_POSIX_SIGNALS -#include "catch.hpp" +#include "util_catch.hpp" +#include "util_check_memory.hpp" namespace ams::test { @@ -70,7 +66,9 @@ namespace ams::test { ON_SCOPE_EXIT { CATCH_REQUIRE(initial_memory == GetPhysicalMemorySizeAvailable()); }; CATCH_SECTION("Unaligned and too big sizes fail") { - CATCH_REQUIRE(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), 5))); + for (size_t i = 1; i < svc::HeapSizeAlignment; i = util::AlignUp(i + 1, os::MemoryPageSize)){ + CATCH_REQUIRE(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), i))); + } CATCH_REQUIRE(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), 64_GB))); } @@ -85,12 +83,7 @@ namespace ams::test { CATCH_SECTION("SetHeapSize gives heap memory") { CATCH_REQUIRE(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment))); - - CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); - CATCH_REQUIRE(mem_info.base_address == addr); - CATCH_REQUIRE(mem_info.size == svc::HeapSizeAlignment); - CATCH_REQUIRE(mem_info.permission == svc::MemoryPermission_ReadWrite); - CATCH_REQUIRE(mem_info.state == svc::MemoryState_Normal); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); CATCH_REQUIRE(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0))); } @@ -99,28 +92,15 @@ namespace ams::test { CATCH_REQUIRE(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment))); CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); - CATCH_REQUIRE(mem_info.base_address == addr); - CATCH_REQUIRE(mem_info.size == svc::HeapSizeAlignment); - CATCH_REQUIRE(mem_info.permission == svc::MemoryPermission_ReadWrite); - CATCH_REQUIRE(mem_info.state == svc::MemoryState_Normal); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(addr, svc::HeapSizeAlignment, svc::MemoryPermission_Read))); - - CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); - CATCH_REQUIRE(mem_info.base_address == addr); - CATCH_REQUIRE(mem_info.size == svc::HeapSizeAlignment); - CATCH_REQUIRE(mem_info.permission == svc::MemoryPermission_Read); - CATCH_REQUIRE(mem_info.state == svc::MemoryState_Normal); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_Read, 0); CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetHeapSize(std::addressof(dummy), 0))); CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(addr, svc::HeapSizeAlignment, svc::MemoryPermission_ReadWrite))); - - CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); - CATCH_REQUIRE(mem_info.base_address == addr); - CATCH_REQUIRE(mem_info.size == svc::HeapSizeAlignment); - CATCH_REQUIRE(mem_info.permission == svc::MemoryPermission_ReadWrite); - CATCH_REQUIRE(mem_info.state == svc::MemoryState_Normal); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); CATCH_REQUIRE(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0))); } diff --git a/tests/TestSvc/source/test_set_memory_permission.cpp b/tests/TestSvc/source/test_set_memory_permission.cpp new file mode 100644 index 000000000..50177554a --- /dev/null +++ b/tests/TestSvc/source/test_set_memory_permission.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "util_catch.hpp" +#include "util_check_memory.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + namespace { + + bool CanSetMemoryPermission(u8 state) { + return state == svc::MemoryState_CodeData || state == svc::MemoryState_AliasCodeData || state == svc::MemoryState_Normal; + } + + } + + alignas(os::MemoryPageSize) constinit u8 g_memory_permission_buffer[2 * os::MemoryPageSize]; + + CATCH_TEST_CASE("svc::SetMemoryPermission invalid arguments") { + const uintptr_t buffer = reinterpret_cast(g_memory_permission_buffer); + + for (size_t i = 1; i < os::MemoryPageSize; ++i) { + CATCH_REQUIRE(svc::ResultInvalidAddress::Includes(svc::SetMemoryPermission(buffer + i, os::MemoryPageSize, svc::MemoryPermission_Read))); + CATCH_REQUIRE(svc::ResultInvalidSize::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize + i, svc::MemoryPermission_Read))); + } + + CATCH_REQUIRE(svc::ResultInvalidSize::Includes(svc::SetMemoryPermission(buffer, 0, svc::MemoryPermission_Read))); + + { + const u64 vmem_end = util::AlignDown(std::numeric_limits::max(), os::MemoryPageSize); + CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(vmem_end, 2 * os::MemoryPageSize, svc::MemoryPermission_Read))); + } + + CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(svc::AddressMap39End, os::MemoryPageSize, svc::MemoryPermission_Read))); + + for (size_t i = 0; i < 0x100; ++i) { + const auto perm = static_cast(i); + if (perm == svc::MemoryPermission_None || perm == svc::MemoryPermission_Read || perm == svc::MemoryPermission_ReadWrite) { + continue; + } + + CATCH_REQUIRE(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, perm))); + } + CATCH_REQUIRE(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_ReadExecute))); + CATCH_REQUIRE(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_Write))); + CATCH_REQUIRE(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_DontCare))); + } + + CATCH_TEST_CASE("svc::SetMemoryPermission works on specific states") { + /* Check that we have CodeData. */ + const uintptr_t bss_buffer = reinterpret_cast(g_memory_permission_buffer); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0); + + /* Create a heap. */ + ScopedHeap scoped_heap(2 * svc::HeapSizeAlignment); + TestMemory(scoped_heap.GetAddress(), scoped_heap.GetSize(), svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + /* TODO: Ensure we have alias code data? */ + + uintptr_t addr = 0; + while (true) { + /* Get current mapping. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); + + /* Try to set permission. */ + if (CanSetMemoryPermission(mem_info.state) && mem_info.attribute == 0) { + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, svc::MemoryPermission_ReadWrite))); + TestMemory(mem_info.base_address, mem_info.size, mem_info.state, svc::MemoryPermission_ReadWrite, mem_info.attribute); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, mem_info.permission))); + } else { + CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, svc::MemoryPermission_Read))); + } + + const uintptr_t next_address = mem_info.base_address + mem_info.size; + if (next_address <= addr) { + break; + } + + addr = next_address; + } + } + + CATCH_TEST_CASE("svc::SetMemoryPermission allows for free movement between RW-, R--, ---") { + /* Define helper. */ + auto test_set_memory_permission = [](uintptr_t address, size_t size){ + /* Get the permission. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address))); + + const svc::MemoryPermission legal_states[] = { svc::MemoryPermission_None, svc::MemoryPermission_Read, svc::MemoryPermission_ReadWrite }; + for (const auto src_state : legal_states) { + for (const auto dst_state : legal_states) { + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(address, size, svc::MemoryPermission_None))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(address, size, src_state))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(address, size, dst_state))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(address, size, svc::MemoryPermission_None))); + } + } + + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(address, size, mem_info.permission))); + }; + + /* Test that we can freely move about .bss buffers. */ + test_set_memory_permission(reinterpret_cast(g_memory_permission_buffer), sizeof(g_memory_permission_buffer)); + + /* Create a heap. */ + ScopedHeap scoped_heap(svc::HeapSizeAlignment); + TestMemory(scoped_heap.GetAddress(), scoped_heap.GetSize(), svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + /* Test that we can freely move about heap. */ + test_set_memory_permission(scoped_heap.GetAddress(), scoped_heap.GetSize()); + + /* TODO: AliasCodeData */ + } + + CATCH_TEST_CASE("svc::SetMemoryPermission fails when the memory has non-zero attribute") { + const uintptr_t bss_buffer = reinterpret_cast(g_memory_permission_buffer); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0); + + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite))); + + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryAttribute(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryAttribute_Uncached, svc::MemoryAttribute_Uncached))); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, svc::MemoryAttribute_Uncached); + + CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None))); + CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read))); + CATCH_REQUIRE(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite))); + + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryAttribute(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryAttribute_Uncached, 0))); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0); + + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read))); + CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite))); + } + +} \ No newline at end of file diff --git a/tests/TestSvc/source/util_catch.hpp b/tests/TestSvc/source/util_catch.hpp new file mode 100644 index 000000000..2dbe1b26c --- /dev/null +++ b/tests/TestSvc/source/util_catch.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#define CATCH_CONFIG_NOSTDOUT +#define CATCH_CONFIG_PREFIX_ALL +#define CATCH_CONFIG_DISABLE_EXCEPTIONS +#define CATCH_CONFIG_NO_POSIX_SIGNALS +#include "catch.hpp" diff --git a/tests/TestSvc/source/util_check_memory.hpp b/tests/TestSvc/source/util_check_memory.hpp new file mode 100644 index 000000000..120fccfcf --- /dev/null +++ b/tests/TestSvc/source/util_check_memory.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include "util_catch.hpp" + +namespace ams::test { + + inline void TestMemory(uintptr_t address, svc::MemoryState state, svc::MemoryPermission perm, u32 attr) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address))); + + CATCH_REQUIRE(mem_info.base_address <= address); + CATCH_REQUIRE(address < (mem_info.base_address + mem_info.size)); + CATCH_REQUIRE(mem_info.state == state); + CATCH_REQUIRE(mem_info.permission == perm); + CATCH_REQUIRE(mem_info.attribute == attr); + } + + inline void TestMemory(uintptr_t address, size_t size, svc::MemoryState state, svc::MemoryPermission perm, u32 attr) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + CATCH_REQUIRE(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address))); + + CATCH_REQUIRE(mem_info.base_address <= address); + CATCH_REQUIRE(mem_info.base_address < (address + size)); + CATCH_REQUIRE((address + size) <= (mem_info.base_address + mem_info.size)); + CATCH_REQUIRE(mem_info.state == state); + CATCH_REQUIRE(mem_info.permission == perm); + CATCH_REQUIRE(mem_info.attribute == attr); + } + +} \ No newline at end of file diff --git a/tests/TestSvc/source/util_scoped_heap.hpp b/tests/TestSvc/source/util_scoped_heap.hpp new file mode 100644 index 000000000..8b3ed6e61 --- /dev/null +++ b/tests/TestSvc/source/util_scoped_heap.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include "util_catch.hpp" + +namespace ams::test { + + class ScopedHeap { + NON_COPYABLE(ScopedHeap); + NON_MOVEABLE(ScopedHeap); + private: + uintptr_t m_address; + size_t m_size; + public: + explicit ScopedHeap(size_t size) { + this->SetHeapSize(size); + } + + ~ScopedHeap() { + const auto result = svc::SetHeapSize(std::addressof(m_address), 0); + CATCH_REQUIRE(R_SUCCEEDED(result)); + } + + void SetHeapSize(size_t size) { + m_size = util::AlignUp(size, svc::HeapSizeAlignment); + + const auto result = svc::SetHeapSize(std::addressof(m_address), m_size); + CATCH_REQUIRE(R_SUCCEEDED(result)); + } + + uintptr_t GetAddress() const { return m_address; } + size_t GetSize() const { return m_size; } + }; + +} \ No newline at end of file