mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-28 06:45:29 +03:00
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:
parent
e8e1b910c3
commit
eb00dc62f8
@ -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 ®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<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())};
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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};
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user