From eb00dc62f8067a71902a89d06faa5a91584eafa0 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sat, 19 Nov 2022 12:32:15 +0000 Subject: [PATCH] Implement support for 36 bit games by using split code/heap mappings Although rtld and IPC prevent TLS/IO and code from being above the 36-bit AS limit, nothing depends the heap being below it. We can take advantage of this by stealing as much AS as possible for code in the lower 36-bits. --- app/src/main/cpp/skyline/kernel/memory.cpp | 173 ++++++++++++------ app/src/main/cpp/skyline/kernel/memory.h | 15 +- .../skyline/kernel/types/KPrivateMemory.cpp | 4 +- .../skyline/kernel/types/KSharedMemory.cpp | 4 +- app/src/main/cpp/skyline/loader/loader.cpp | 13 +- 5 files changed, 137 insertions(+), 72 deletions(-) diff --git a/app/src/main/cpp/skyline/kernel/memory.cpp b/app/src/main/cpp/skyline/kernel/memory.cpp index d8b16328..9b14050e 100644 --- a/app/src/main/cpp/skyline/kernel/memory.cpp +++ b/app/src/main/cpp/skyline/kernel/memory.cpp @@ -17,7 +17,53 @@ namespace skyline::kernel { constexpr size_t RegionAlignment{1ULL << 21}; //!< The minimum alignment of a HOS memory region constexpr size_t CodeRegionSize{4ULL * 1024 * 1024 * 1024}; //!< The assumed maximum size of the code region (4GiB) + static std::pair, FileDescriptor> AllocateMappedRange(size_t minSize, size_t align, size_t minAddress, size_t maxAddress, bool findLargest) { + span region{}; + size_t size{minSize}; + + std::ifstream mapsFile("/proc/self/maps"); + std::string maps((std::istreambuf_iterator(mapsFile)), std::istreambuf_iterator()); + size_t line{}, start{minAddress}, alignedStart{minAddress}; + do { + auto end{util::HexStringToInt(std::string_view(maps.data() + line, sizeof(u64) * 2))}; + if (end < start) + continue; + if (end - start > size + (alignedStart - start)) { // We don't want to overflow if alignedStart > start + if (findLargest) + size = end - start; + + region = span{reinterpret_cast(alignedStart), size}; + + if (!findLargest) + break; + } + + start = util::HexStringToInt(std::string_view(maps.data() + maps.find_first_of('-', line) + 1, sizeof(u64) * 2)); + alignedStart = util::AlignUp(start, align); + if (alignedStart + size > maxAddress) // We don't want to map past the end of the address space + break; + } while ((line = maps.find_first_of('\n', line)) != std::string::npos && line++); + + if (!region.valid()) + throw exception("Allocation failed"); + + FileDescriptor memoryFd{static_cast(syscall(__NR_memfd_create, "HOS-AS", MFD_CLOEXEC))}; // We need to use memfd directly as ASharedMemory doesn't always use it while we depend on it for FreeMemory (using FALLOC_FL_PUNCH_HOLE) to work + if (memoryFd == -1) + throw exception("Failed to create memfd for guest address space: {}", strerror(errno)); + + if (ftruncate(memoryFd, static_cast(size)) == -1) + throw exception("Failed to resize memfd for guest address space: {}", strerror(errno)); + + auto result{mmap(reinterpret_cast(region.data()), size, PROT_WRITE, MAP_FIXED | MAP_SHARED, memoryFd, 0)}; + if (result == MAP_FAILED) + throw exception("Failed to mmap guest address space: {}", strerror(errno)); + + return {region, memoryFd}; + } + void MemoryManager::InitializeVmm(memory::AddressSpaceType type) { + addressSpaceType = type; + size_t baseSize{}; switch (type) { case memory::AddressSpaceType::AddressSpace32Bit: @@ -25,9 +71,8 @@ namespace skyline::kernel { throw exception("32-bit address spaces are not supported"); case memory::AddressSpaceType::AddressSpace36Bit: { - addressSpace = span{reinterpret_cast(0), 1ULL << 36}; - baseSize = 0x78000000 + 0x180000000 + 0x78000000 + 0x180000000; - throw exception("36-bit address spaces are not supported"); // Due to VMM base being forced at 0x800000 and it being used by ART + addressSpace = span{reinterpret_cast(0x8000000), (1ULL << 39) - 0x8000000}; + baseSize = 0x180000000 + 0x78000000 + 0x180000000; } case memory::AddressSpaceType::AddressSpace39Bit: { @@ -40,74 +85,80 @@ namespace skyline::kernel { throw exception("VMM initialization with unknown address space"); } - // Search for a suitable carveout in host AS to fit the guest AS inside of - std::ifstream mapsFile("/proc/self/maps"); - std::string maps((std::istreambuf_iterator(mapsFile)), std::istreambuf_iterator()); - size_t line{}, start{1ULL << 35}, alignedStart{1ULL << 35}; // Qualcomm KGSL (Kernel Graphic Support Layer/Kernel GPU driver) maps below 35-bits, reserving it causes KGSL to go OOM - do { - auto end{util::HexStringToInt(std::string_view(maps.data() + line, sizeof(u64) * 2))}; - if (end < start) - continue; - if (end - start > baseSize + (alignedStart - start)) { // We don't want to overflow if alignedStart > start - base = span{reinterpret_cast(alignedStart), baseSize}; - break; - } + // Qualcomm KGSL (Kernel Graphic Support Layer/Kernel GPU driver) maps below 35-bits, reserving it causes KGSL to go OOM + static constexpr size_t KgslReservedRegionSize{1ULL << 35}; + if (type != memory::AddressSpaceType::AddressSpace36Bit) { + std::tie(base, memoryFd) = AllocateMappedRange(baseSize, RegionAlignment, KgslReservedRegionSize, addressSpace.size(), false); - start = util::HexStringToInt(std::string_view(maps.data() + maps.find_first_of('-', line) + 1, sizeof(u64) * 2)); - alignedStart = util::AlignUp(start, RegionAlignment); - if (alignedStart + baseSize > addressSpace.size()) // We don't want to map past the end of the address space - break; - } while ((line = maps.find_first_of('\n', line)) != std::string::npos && line++); + chunks = { + ChunkDescriptor{ + .ptr = addressSpace.data(), + .size = static_cast(base.data() - addressSpace.data()), + .state = memory::states::Reserved, + }, + ChunkDescriptor{ + .ptr = base.data(), + .size = base.size(), + .state = memory::states::Unmapped, + }, + ChunkDescriptor{ + .ptr = base.end().base(), + .size = addressSpace.size() - reinterpret_cast(base.end().base()), + .state = memory::states::Reserved, + }}; - if (!base.valid()) - throw exception("Cannot find a suitable carveout for the guest address space"); + code = base; - memoryFd = static_cast(syscall(__NR_memfd_create, "HOS-AS", MFD_CLOEXEC)); // We need to use memfd directly as ASharedMemory doesn't always use it while we depend on it for FreeMemory (using FALLOC_FL_PUNCH_HOLE) to work - if (memoryFd == -1) - throw exception("Failed to create memfd for guest address space: {}", strerror(errno)); + } else { + std::tie(base, memoryFd) = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false); + std::tie(codeBase36Bit, code36BitFd) = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast(addressSpace.data()), true); - if (ftruncate(memoryFd, static_cast(base.size())) == -1) - throw exception("Failed to resize memfd for guest address space: {}", strerror(errno)); - - auto result{mmap(reinterpret_cast(base.data()), base.size(), PROT_WRITE, MAP_FIXED | MAP_SHARED, memoryFd, 0)}; - if (result == MAP_FAILED) - throw exception("Failed to mmap guest address space: {}", strerror(errno)); - - chunks = { - ChunkDescriptor{ - .ptr = addressSpace.data(), - .size = static_cast(base.data() - addressSpace.data()), - .state = memory::states::Reserved, - }, - ChunkDescriptor{ - .ptr = base.data(), - .size = base.size(), - .state = memory::states::Unmapped, - }, - ChunkDescriptor{ - .ptr = base.end().base(), - .size = addressSpace.size() - reinterpret_cast(base.end().base()), - .state = memory::states::Reserved, - }}; + chunks = { + ChunkDescriptor{ + .ptr = addressSpace.data(), + .size = static_cast(codeBase36Bit.data() - addressSpace.data()), + .state = memory::states::Heap, // We can't use reserved here as rtld uses it to know when to halt memory walking + }, + ChunkDescriptor{ + .ptr = codeBase36Bit.data(), + .size = codeBase36Bit.size(), + .state = memory::states::Unmapped, + }, + ChunkDescriptor{ + .ptr = codeBase36Bit.end().base(), + .size = static_cast(base.data() - codeBase36Bit.end().base()), + .state = memory::states::Heap, + }, + ChunkDescriptor{ + .ptr = base.data(), + .size = base.size(), + .state = memory::states::Unmapped, + }, + ChunkDescriptor{ + .ptr = base.end().base(), + .size = addressSpace.size() - reinterpret_cast(base.end().base()), + .state = memory::states::Reserved, + }}; + code = codeBase36Bit; + } } void MemoryManager::InitializeRegions(span codeRegion) { if (!util::IsAligned(codeRegion.data(), RegionAlignment)) throw exception("Non-aligned code region was used to initialize regions: 0x{:X} - 0x{:X}", codeRegion.data(), codeRegion.end().base()); - switch (addressSpace.size()) { - case 1UL << 36: { - code = span{reinterpret_cast(0x800000), 0x78000000}; - if (code.data() > codeRegion.data() || (code.end().base() < codeRegion.end().base())) - throw exception("Code mapping larger than 36-bit code region"); - alias = span{code.end().base(), 0x180000000}; - stack = span{alias.end().base(), 0x78000000}; + switch (addressSpaceType) { + case memory::AddressSpaceType::AddressSpace36Bit: { + // Place code, stack and TLS/IO in the lower 36-bits of the host AS and heap past that + code = span{codeBase36Bit.data(), util::AlignUp(codeRegion.size(), RegionAlignment)}; + stack = span{code.end().base(), codeBase36Bit.size() - code.size()}; tlsIo = stack; //!< TLS/IO is shared with Stack on 36-bit - heap = span{stack.end().base(), 0x180000000}; + alias = span{base.data(), 0x180000000}; + heap = span{alias.end().base(), 0x180000000}; break; } - case 1UL << 39: { + case memory::AddressSpaceType::AddressSpace39Bit: { code = span{base.data(), util::AlignUp(codeRegion.size(), RegionAlignment)}; alias = span{code.end().base(), 0x1000000000}; heap = span{alias.end().base(), 0x180000000}; @@ -120,7 +171,7 @@ namespace skyline::kernel { throw exception("Regions initialized without VMM initialization"); } - auto newSize{code.size() + alias.size() + stack.size() + heap.size() + ((addressSpace.size() == 1UL << 39) ? tlsIo.size() : 0)}; + auto newSize{code.size() + alias.size() + stack.size() + heap.size() + ((addressSpaceType == memory::AddressSpaceType::AddressSpace39Bit) ? tlsIo.size() : 0)}; if (newSize > base.size()) throw exception("Guest VMM size has exceeded host carveout size: 0x{:X}/0x{:X} (Code: 0x{:X}/0x{:X})", newSize, base.size(), code.size(), CodeRegionSize); if (newSize != base.size()) @@ -133,7 +184,7 @@ namespace skyline::kernel { } span MemoryManager::CreateMirror(span mapping) { - if (mapping.data() < base.data() || mapping.end().base() > base.end().base()) + if (!base.contains(mapping)) throw exception("Mapping is outside of VMM base: 0x{:X} - 0x{:X}", mapping.data(), mapping.end().base()); auto offset{static_cast(mapping.data() - base.data())}; @@ -158,7 +209,7 @@ namespace skyline::kernel { size_t mirrorOffset{}; for (const auto ®ion : regions) { - if (region.data() < base.data() || region.end().base() > base.end().base()) + if (!base.contains(region)) throw exception("Mapping is outside of VMM base: 0x{:X} - 0x{:X}", region.data(), region.end().base()); auto offset{static_cast(region.data() - base.data())}; @@ -179,7 +230,7 @@ namespace skyline::kernel { } void MemoryManager::FreeMemory(span memory) { - if (memory.data() < base.data() || memory.end().base() > base.end().base()) + if (!base.contains(memory)) throw exception("Mapping is outside of VMM base: 0x{:X} - 0x{:X}", memory.data(), memory.end().base()); auto offset{static_cast(memory.data() - base.data())}; diff --git a/app/src/main/cpp/skyline/kernel/memory.h b/app/src/main/cpp/skyline/kernel/memory.h index f6266722..dc3ba27b 100644 --- a/app/src/main/cpp/skyline/kernel/memory.h +++ b/app/src/main/cpp/skyline/kernel/memory.h @@ -215,8 +215,10 @@ namespace skyline { std::vector chunks; public: + memory::AddressSpaceType addressSpaceType{}; span addressSpace{}; //!< The entire address space - span base{}; //!< The application-accessible address space + span codeBase36Bit{}; //!< A mapping in the lower 36 bits of the address space for mapping code and stack on 36-bit guests + span base{}; //!< The application-accessible address space (for 39-bit guests) or the heap/alias address space (for 36-bit guests) span code{}; span alias{}; span heap{}; @@ -224,6 +226,7 @@ namespace skyline { span tlsIo{}; //!< TLS/IO FileDescriptor memoryFd{}; //!< The file descriptor of the memory backing for the entire guest address space + FileDescriptor code36BitFd{}; //!< The file descriptor of the memory backing for in the lower 36 bits of the address space for mapping code and stack on 36-bit guests std::shared_mutex mutex; //!< Synchronizes any operations done on the VMM, it's locked in shared mode by readers and exclusive mode by writers @@ -274,6 +277,16 @@ namespace skyline { * @note There is a ceiling of SystemResourceSize as specified in the NPDM, this value will be clipped to that */ size_t GetSystemResourceUsage(); + + /** + * @return If the supplied region is contained withing the accessible guest address space + */ + bool AddressSpaceContains(span region) const { + if (addressSpaceType == memory::AddressSpaceType::AddressSpace36Bit) + return codeBase36Bit.contains(region) || base.contains(region); + else + return base.contains(region); + } }; } } diff --git a/app/src/main/cpp/skyline/kernel/types/KPrivateMemory.cpp b/app/src/main/cpp/skyline/kernel/types/KPrivateMemory.cpp index 52d18989..c4728a19 100644 --- a/app/src/main/cpp/skyline/kernel/types/KPrivateMemory.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KPrivateMemory.cpp @@ -13,7 +13,7 @@ namespace skyline::kernel::type { memoryState(memState), handle(handle), KMemory(state, KType::KPrivateMemory, guest) { - if (!state.process->memory.base.contains(guest)) + if (!state.process->memory.AddressSpaceContains(guest)) throw exception("KPrivateMemory allocation isn't inside guest address space: 0x{:X} - 0x{:X}", guest.data(), guest.data() + guest.size()); if (!util::IsPageAligned(guest.data()) || !util::IsPageAligned(guest.size())) throw exception("KPrivateMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} (0x{:X})", guest.data(), guest.data() + guest.size(), guest.size()); @@ -53,7 +53,7 @@ namespace skyline::kernel::type { } void KPrivateMemory::Remap(span map) { - if (!state.process->memory.base.contains(map)) + if (!state.process->memory.AddressSpaceContains(map)) throw exception("KPrivateMemory remapping isn't inside guest address space: 0x{:X} - 0x{:X}", map.data(), map.end().base()); if (!util::IsPageAligned(map.data()) || !util::IsPageAligned(map.size())) throw exception("KPrivateMemory remapping isn't page-aligned: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size()); diff --git a/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp b/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp index acd0cc5a..3400967f 100644 --- a/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp @@ -23,7 +23,7 @@ namespace skyline::kernel::type { } u8 *KSharedMemory::Map(span map, memory::Permission permission) { - if (!state.process->memory.base.contains(map)) + if (!state.process->memory.AddressSpaceContains(map)) throw exception("KPrivateMemory allocation isn't inside guest address space: 0x{:X} - 0x{:X}", map.data(), map.end().base()); if (!util::IsPageAligned(map.data()) || !util::IsPageAligned(map.size())) throw exception("KSharedMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size()); @@ -51,7 +51,7 @@ namespace skyline::kernel::type { void KSharedMemory::Unmap(span map) { auto &memoryManager{state.process->memory}; - if (!memoryManager.base.contains(map)) + if (!memoryManager.AddressSpaceContains(map)) throw exception("KPrivateMemory allocation isn't inside guest address space: 0x{:X} - 0x{:X}", map.data(), map.end().base()); if (!util::IsPageAligned(map.data()) || !util::IsPageAligned(map.size())) throw exception("KSharedMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size()); diff --git a/app/src/main/cpp/skyline/loader/loader.cpp b/app/src/main/cpp/skyline/loader/loader.cpp index bdcec68a..b2248f06 100644 --- a/app/src/main/cpp/skyline/loader/loader.cpp +++ b/app/src/main/cpp/skyline/loader/loader.cpp @@ -12,7 +12,7 @@ namespace skyline::loader { Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr &process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name, bool dynamicallyLinked) { - u8 *base{reinterpret_cast(process->memory.base.data() + offset)}; + u8 *base{reinterpret_cast(process->memory.code.data() + offset)}; size_t textSize{executable.text.contents.size()}; size_t roSize{executable.ro.contents.size()}; @@ -70,20 +70,21 @@ namespace skyline::loader { hookSize = util::AlignUp(state.nce->GetHookSectionSize(executableSymbols), PAGE_SIZE); } - process->NewHandle(span{base, patch.size + hookSize}, memory::Permission{false, false, false}, memory::states::Reserved); // --- - Logger::Error("Successfully mapped section .patch @ 0x{:X}, Size = 0x{:X}", base, patch.size); + auto patchType{process->memory.addressSpaceType == memory::AddressSpaceType::AddressSpace36Bit ? memory::states::Heap : memory::states::Reserved}; + process->NewHandle(span{base, patch.size + hookSize}, memory::Permission{false, false, false}, patchType); // --- + Logger::Debug("Successfully mapped section .patch @ 0x{:X}, Size = 0x{:X}", base, patch.size); if (hookSize > 0) Logger::Error("Successfully mapped section .hook @ 0x{:X}, Size = 0x{:X}", base + patch.size, hookSize); u8 *executableBase{base + patch.size + hookSize}; process->NewHandle(span{executableBase + executable.text.offset, textSize}, memory::Permission{true, false, true}, memory::states::CodeStatic); // R-X - Logger::Error("Successfully mapped section .text @ 0x{:X}, Size = 0x{:X}", executableBase, textSize); + Logger::Debug("Successfully mapped section .text @ 0x{:X}, Size = 0x{:X}", executableBase, textSize); process->NewHandle(span{executableBase + executable.ro.offset, roSize}, memory::Permission{true, false, false}, memory::states::CodeStatic); // R-- - Logger::Error("Successfully mapped section .rodata @ 0x{:X}, Size = 0x{:X}", executableBase + executable.ro.offset, roSize); + Logger::Debug("Successfully mapped section .rodata @ 0x{:X}, Size = 0x{:X}", executableBase + executable.ro.offset, roSize); process->NewHandle(span{executableBase + executable.data.offset, dataSize}, memory::Permission{true, true, false}, memory::states::CodeMutable); // RW- - Logger::Error("Successfully mapped section .data + .bss @ 0x{:X}, Size = 0x{:X}", executableBase + executable.data.offset, dataSize); + Logger::Debug("Successfully mapped section .data + .bss @ 0x{:X}, Size = 0x{:X}", executableBase + executable.data.offset, dataSize); size_t size{patch.size + hookSize + textSize + roSize + dataSize}; {