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.
This commit is contained in:
Billy Laws 2022-11-19 12:32:15 +00:00
parent e8e1b910c3
commit eb00dc62f8
5 changed files with 137 additions and 72 deletions

View File

@ -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<span<u8>, FileDescriptor> AllocateMappedRange(size_t minSize, size_t align, size_t minAddress, size_t maxAddress, bool findLargest) {
span<u8> region{};
size_t size{minSize};
std::ifstream mapsFile("/proc/self/maps");
std::string maps((std::istreambuf_iterator<char>(mapsFile)), std::istreambuf_iterator<char>());
size_t line{}, start{minAddress}, alignedStart{minAddress};
do {
auto end{util::HexStringToInt<u64>(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<u8>{reinterpret_cast<u8 *>(alignedStart), size};
if (!findLargest)
break;
}
start = util::HexStringToInt<u64>(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<int>(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<off_t>(size)) == -1)
throw exception("Failed to resize memfd for guest address space: {}", strerror(errno));
auto result{mmap(reinterpret_cast<void *>(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<u8>{reinterpret_cast<u8 *>(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<u8>{reinterpret_cast<u8 *>(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<char>(mapsFile)), std::istreambuf_iterator<char>());
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<u64>(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<u8>{reinterpret_cast<u8 *>(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<u64>(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<size_t>(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<u64>(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<int>(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<size_t>(addressSpace.data()), true);
if (ftruncate(memoryFd, static_cast<off_t>(base.size())) == -1)
throw exception("Failed to resize memfd for guest address space: {}", strerror(errno));
auto result{mmap(reinterpret_cast<void *>(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<size_t>(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<u64>(base.end().base()),
.state = memory::states::Reserved,
}};
chunks = {
ChunkDescriptor{
.ptr = addressSpace.data(),
.size = static_cast<size_t>(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<u64>(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<u64>(base.end().base()),
.state = memory::states::Reserved,
}};
code = codeBase36Bit;
}
}
void MemoryManager::InitializeRegions(span<u8> 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<u8>{reinterpret_cast<u8 *>(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<u8>{code.end().base(), 0x180000000};
stack = span<u8>{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<u8>{codeBase36Bit.data(), util::AlignUp(codeRegion.size(), RegionAlignment)};
stack = span<u8>{code.end().base(), codeBase36Bit.size() - code.size()};
tlsIo = stack; //!< TLS/IO is shared with Stack on 36-bit
heap = span<u8>{stack.end().base(), 0x180000000};
alias = span<u8>{base.data(), 0x180000000};
heap = span<u8>{alias.end().base(), 0x180000000};
break;
}
case 1UL << 39: {
case memory::AddressSpaceType::AddressSpace39Bit: {
code = span<u8>{base.data(), util::AlignUp(codeRegion.size(), RegionAlignment)};
alias = span<u8>{code.end().base(), 0x1000000000};
heap = span<u8>{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<u8> MemoryManager::CreateMirror(span<u8> 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<size_t>(mapping.data() - base.data())};
@ -158,7 +209,7 @@ namespace skyline::kernel {
size_t mirrorOffset{};
for (const auto &region : 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<size_t>(region.data() - base.data())};
@ -179,7 +230,7 @@ namespace skyline::kernel {
}
void MemoryManager::FreeMemory(span<u8> 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<size_t>(memory.data() - base.data())};

View File

@ -215,8 +215,10 @@ namespace skyline {
std::vector<ChunkDescriptor> chunks;
public:
memory::AddressSpaceType addressSpaceType{};
span<u8> addressSpace{}; //!< The entire address space
span<u8> base{}; //!< The application-accessible address space
span<u8> codeBase36Bit{}; //!< A mapping in the lower 36 bits of the address space for mapping code and stack on 36-bit guests
span<u8> base{}; //!< The application-accessible address space (for 39-bit guests) or the heap/alias address space (for 36-bit guests)
span<u8> code{};
span<u8> alias{};
span<u8> heap{};
@ -224,6 +226,7 @@ namespace skyline {
span<u8> 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<u8> region) const {
if (addressSpaceType == memory::AddressSpaceType::AddressSpace36Bit)
return codeBase36Bit.contains(region) || base.contains(region);
else
return base.contains(region);
}
};
}
}

View File

@ -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<u8> 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());

View File

@ -23,7 +23,7 @@ namespace skyline::kernel::type {
}
u8 *KSharedMemory::Map(span<u8> 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<u8> 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());

View File

@ -12,7 +12,7 @@
namespace skyline::loader {
Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name, bool dynamicallyLinked) {
u8 *base{reinterpret_cast<u8 *>(process->memory.base.data() + offset)};
u8 *base{reinterpret_cast<u8 *>(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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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};
{