Dynamic Guest Memory Base Allocation

This commit is contained in:
◱ PixelyIon 2020-10-14 02:13:52 +05:30 committed by ◱ PixelyIon
parent cffbfc8034
commit 369bd469f6
21 changed files with 111 additions and 57 deletions

View File

@ -68,9 +68,6 @@ namespace skyline {
};
namespace constant {
// Memory
constexpr u64 BaseAddress{0x8000000}; //!< The address space base
constexpr u64 DefaultStackSize{0x1E8480}; //!< The default amount of stack: 2 MB
// Display
constexpr u16 HandheldResolutionW{1280}; //!< The width component of the handheld resolution
constexpr u16 HandheldResolutionH{720}; //!< The height component of the handheld resolution
@ -140,12 +137,12 @@ namespace skyline {
template<class T>
T PointerValue(T item) {
return item;
};
}
template<class T>
size_t PointerValue(T *item) {
return reinterpret_cast<size_t>(item);
};
}
/**
* @return The value aligned up to the next multiple
@ -210,7 +207,7 @@ namespace skyline {
return object;
}
constexpr u8 HexDigitToByte(char digit) {
constexpr u8 HexDigitToNibble(char digit) {
if (digit >= '0' && digit <= '9')
return digit - '0';
else if (digit >= 'a' && digit <= 'f')
@ -221,17 +218,35 @@ namespace skyline {
}
template<size_t Size>
constexpr std::array<u8, Size> HexStringToArray(std::string_view hexString) {
if (hexString.size() != Size * 2)
constexpr std::array<u8, Size> HexStringToArray(std::string_view string) {
if (string.size() != Size * 2)
throw exception("Invalid size");
std::array<u8, Size> result;
for (size_t i{}; i < Size; i++) {
size_t hexStrIndex{i * 2};
result[i] = (HexDigitToByte(hexString[hexStrIndex]) << 4) | HexDigitToByte(hexString[hexStrIndex + 1]);
size_t index{i * 2};
result[i] = (HexDigitToNibble(string[index]) << 4) | HexDigitToNibble(string[index + 1]);
}
return result;
}
template<class Type>
constexpr Type HexStringToInt(std::string_view string) {
Type result{};
size_t offset{(sizeof(Type) * 8) - 4};
for (size_t index{}; index < std::min(sizeof(Type) * 2, string.size()); index++, offset -= 4) {
char digit{string[index]};
if (digit >= '0' && digit <= '9')
result |= static_cast<Type>(digit - '0') << offset;
else if (digit >= 'a' && digit <= 'f')
result |= static_cast<Type>(digit - 'a' + 10) << offset;
else if (digit >= 'A' && digit <= 'F')
result |= static_cast<Type>(digit - 'A' + 10) << offset;
else
break;
}
return result >> (offset + 4);
}
/**
* @brief A compile-time hash function as std::hash isn't constexpr
*/

View File

@ -15,16 +15,14 @@ namespace skyline::kernel {
case memory::AddressSpaceType::AddressSpace36Bit: {
addressSpace.address = 0;
addressSpace.size = 1UL << 36;
base.address = constant::BaseAddress;
base.size = 0xFF8000000;
base.size = 0x78000000 + 0x180000000 + 0x180000000 + 0x180000000;
break;
}
case memory::AddressSpaceType::AddressSpace39Bit: {
addressSpace.address = 0;
addressSpace.size = 1UL << 39;
base.address = constant::BaseAddress;
base.size = 0x7FF8000000;
base.size = 0x78000000 + 0x1000000000 + 0x180000000 + 0x80000000 + 0x1000000000;
break;
}
@ -32,6 +30,27 @@ namespace skyline::kernel {
throw exception("VMM initialization with unknown address space");
}
std::ifstream mapsFile("/proc/self/maps");
std::string maps((std::istreambuf_iterator<char>(mapsFile)), std::istreambuf_iterator<char>());
size_t line{}, start{}, alignedStart{};
do {
auto end{util::HexStringToInt<u64>(std::string_view(maps.data() + line, sizeof(u64) * 2))};
if (end - start > base.size + (alignedStart - start)) { // We don't want to overflow if alignedStart > start
base.address = alignedStart;
break;
}
start = util::HexStringToInt<u64>(std::string_view(maps.data() + maps.find_first_of('-', line) + 1, sizeof(u64) * 2));
alignedStart = util::AlignUp(start, 1ULL << 21);
if (alignedStart + base.size > addressSpace.size)
break;
} while ((line = maps.find_first_of('\n', line)) != std::string::npos && line++);
if (!base.address)
throw exception("Cannot find a suitable carveout for the guest address space");
mmap(reinterpret_cast<void*>(base.address), base.size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
chunks = {ChunkDescriptor{
.ptr = reinterpret_cast<u8 *>(addressSpace.address),
.size = addressSpace.size,
@ -50,7 +69,7 @@ namespace skyline::kernel {
alias.address = code.address + code.size;
alias.size = 0x180000000;
stack.address = alias.address;
stack.size = alias.size;
stack.size = 0x180000000;
heap.address = alias.address + alias.size;
heap.size = 0x180000000;
tlsIo.address = code.address;
@ -59,8 +78,8 @@ namespace skyline::kernel {
}
case 1UL << 39: {
code.address = util::AlignDown(address, 0x200000);
code.size = util::AlignUp(address + size, 0x200000) - code.address;
code.address = base.address;
code.size = 0x78000000;
alias.address = code.address + code.size;
alias.size = 0x1000000000;
heap.address = alias.address + alias.size;
@ -76,7 +95,10 @@ namespace skyline::kernel {
throw exception("Regions initialized without VMM initialization");
}
state.logger->Debug("Region Map:\nCode Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nAlias Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nHeap Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nStack Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nTLS/IO Region: 0x{:X} - 0x{:X} (Size: 0x{:X})", code.address, code.address + code.size, code.size, alias.address, alias.address + alias.size, alias.size, heap.address, heap
if (size > code.size)
throw exception("Code region ({}) is smaller than mapped code size ({})", code.size, size);
state.logger->Debug("Region Map:\nVMM Base: 0x{:X}\nCode Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nAlias Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nHeap Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nStack Region: 0x{:X} - 0x{:X} (Size: 0x{:X})\nTLS/IO Region: 0x{:X} - 0x{:X} (Size: 0x{:X})", base.address, code.address, code.address + code.size, code.size, alias.address, alias.address + alias.size, alias.size, heap.address, heap
.address + heap.size, heap.size, stack.address, stack.address + stack.size, stack.size, tlsIo.address, tlsIo.address + tlsIo.size, tlsIo.size);
}

View File

@ -674,15 +674,15 @@ namespace skyline::kernel::svc {
break;
case constant::infoState::TotalMemoryUsage:
out = state.process->heap->size + constant::DefaultStackSize + state.process->memory.GetProgramSize();
out = state.process->heap->size + state.thread->stack->size + state.process->memory.GetProgramSize();
break;
case constant::infoState::AddressSpaceBaseAddr:
out = state.process->memory.base.address;
out = state.process->memory.addressSpace.address;
break;
case constant::infoState::AddressSpaceSize:
out = state.process->memory.base.size;
out = state.process->memory.addressSpace.size;
break;
case constant::infoState::StackRegionBaseAddr:
@ -698,7 +698,7 @@ namespace skyline::kernel::svc {
break;
case constant::infoState::PersonalMmHeapUsage:
out = state.process->heap->size + constant::DefaultStackSize;
out = state.process->heap->size + state.thread->stack->size;
break;
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
@ -706,7 +706,7 @@ namespace skyline::kernel::svc {
break;
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
out = state.process->heap->size + constant::DefaultStackSize; // TODO: Same as above
out = state.process->heap->size + state.thread->stack->size; // TODO: Same as above
break;
case constant::infoState::UserExceptionContextAddr:

View File

@ -8,16 +8,17 @@
#include "KProcess.h"
namespace skyline::kernel::type {
KPrivateMemory::KPrivateMemory(const DeviceState &state, u8* ptr, size_t size, memory::Permission permission, memory::MemoryState memState) : size(size), permission(permission), memState(memState), KMemory(state, KType::KPrivateMemory) {
if (ptr && !util::PageAligned(ptr))
KPrivateMemory::KPrivateMemory(const DeviceState &state, u8* ptr, size_t size, memory::Permission permission, memory::MemoryState memState) : ptr(ptr), size(size), permission(permission), memState(memState), KMemory(state, KType::KPrivateMemory) {
if (!state.process->memory.base.IsInside(ptr) || !state.process->memory.base.IsInside(ptr + size))
throw exception("KPrivateMemory allocation isn't inside guest address space: 0x{:X} - 0x{:X}", ptr, ptr + size);
if (!util::PageAligned(ptr))
throw exception("KPrivateMemory was created with non-page-aligned address: 0x{:X}", ptr);
this->ptr = reinterpret_cast<u8*>(mmap(ptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, (ptr ? MAP_FIXED : 0) | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (this->ptr == MAP_FAILED)
if (mprotect(ptr, size, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) // We only need to reprotect as the allocation has already been reserved by the MemoryManager
throw exception("An occurred while mapping private memory: {} with 0x{:X} @ 0x{:X}", strerror(errno), ptr, size);
state.process->memory.InsertChunk(ChunkDescriptor{
.ptr = this->ptr,
.ptr = ptr,
.size = size,
.permission = permission,
.state = memState,
@ -25,8 +26,7 @@ namespace skyline::kernel::type {
}
void KPrivateMemory::Resize(size_t nSize) {
ptr = reinterpret_cast<u8*>(mremap(ptr, size, nSize, 0));
if (ptr == MAP_FAILED)
if (mprotect(ptr, nSize, PROT_READ | PROT_WRITE | PROT_EXEC) < 0)
throw exception("An occurred while resizing private memory: {}", strerror(errno));
if (nSize < size) {
@ -48,6 +48,9 @@ namespace skyline::kernel::type {
}
void KPrivateMemory::UpdatePermission(u8* ptr, size_t size, memory::Permission permission) {
ptr = std::clamp(ptr, this->ptr, this->ptr + this->size);
size = std::min(size, static_cast<size_t>((this->ptr + this->size) - ptr));
if (ptr && !util::PageAligned(ptr))
throw exception("KPrivateMemory permission updated with a non-page-aligned address: 0x{:X}", ptr);
@ -64,7 +67,7 @@ namespace skyline::kernel::type {
}
KPrivateMemory::~KPrivateMemory() {
munmap(ptr, size);
mprotect(ptr, size, PROT_NONE);
state.process->memory.InsertChunk(ChunkDescriptor{
.ptr = ptr,
.size = size,

View File

@ -17,8 +17,8 @@ namespace skyline::kernel::type {
memory::MemoryState memState;
/**
* @param ptr The address to map to (If NULL then an arbitrary address is picked)
* @param permission The permissions for the allocated memory (As reported to the application, host memory permissions aren't reflected by this)
* @note 'ptr' needs to be in guest-reserved address space
*/
KPrivateMemory(const DeviceState &state, u8* ptr, size_t size, memory::Permission permission, memory::MemoryState memState);

View File

@ -21,6 +21,8 @@ namespace skyline::kernel::type {
}
u8 *KSharedMemory::Map(u8 *ptr, u64 size, memory::Permission permission) {
if (!state.process->memory.base.IsInside(ptr) || !state.process->memory.base.IsInside(ptr + size))
throw exception("KPrivateMemory allocation isn't inside guest address space: 0x{:X} - 0x{:X}", ptr, ptr + size);
if (ptr && !util::PageAligned(ptr))
throw exception("KSharedMemory was mapped to a non-page-aligned address: 0x{:X}", ptr);
@ -63,7 +65,7 @@ namespace skyline::kernel::type {
munmap(kernel.ptr, kernel.size);
if (guest.Valid()) {
munmap(guest.ptr, guest.size);
mmap(guest.ptr, guest.size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
state.process->memory.InsertChunk(ChunkDescriptor{
.ptr = guest.ptr,
.size = guest.size,

View File

@ -27,8 +27,7 @@ namespace skyline::kernel::type {
KSharedMemory(const DeviceState &state, size_t size, memory::MemoryState memState = memory::states::SharedMemory, KType type = KType::KSharedMemory);
/**
* @param ptr The address to map to (If NULL an arbitrary address is picked, it may be outside of the HOS address space)
* @return The address of the allocation
* @note 'ptr' needs to be in guest-reserved address space
*/
u8 *Map(u8 *ptr, u64 size, memory::Permission permission);

View File

@ -123,7 +123,8 @@ namespace skyline::kernel::type {
state.logger->Debug("Starting thread #{}", id);
if (!stack) {
stack = stack.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.stack.address), constant::DefaultStackSize, memory::Permission{true, true, false}, memory::states::Stack);
constexpr u64 DefaultStackSize{0x1E8480}; //!< The default amount of stack: 2 MB
stack = stack.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.stack.address), DefaultStackSize, memory::Permission{true, true, false}, memory::states::Stack);
if (mprotect(stack->ptr, PAGE_SIZE, PROT_NONE))
throw exception("Failed to create guard page for thread stack at 0x{:X}", stack->ptr);
}

View File

@ -11,6 +11,9 @@ namespace skyline::kernel::type {
*/
class KTransferMemory : public KSharedMemory {
public:
/**
* @note 'ptr' needs to be in guest-reserved address space
*/
KTransferMemory(const DeviceState &state, u8 *ptr, size_t size, memory::Permission permission, memory::MemoryState memState = memory::states::TransferMemory) : KSharedMemory(state, size, memState, KType::KTransferMemory) {
std::memcpy(kernel.ptr, ptr, size);
Map(ptr, size, permission);

View File

@ -9,7 +9,7 @@
namespace skyline::loader {
Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset) {
u8 *base{reinterpret_cast<u8 *>(constant::BaseAddress + offset)};
u8 *base{reinterpret_cast<u8 *>(process->memory.base.address + offset)};
u64 textSize{executable.text.contents.size()};
u64 roSize{executable.ro.contents.size()};
@ -45,6 +45,6 @@ namespace skyline::loader {
std::memcpy(base + executable.data.offset, executable.data.contents.data(), dataSize - executable.bssSize);
std::memcpy(base + patchOffset, patch.data(), patchSize);
return {base, patchOffset + patchSize + padding};
return {base, patchOffset + patchSize + padding, base};
}
}

View File

@ -53,6 +53,7 @@ namespace skyline::loader {
struct ExecutableLoadInfo {
u8* base; //!< The base of the loaded executable
size_t size; //!< The total size of the loaded executable
void* entry; //!< The entry point of the loaded executable
};
/**
@ -74,6 +75,9 @@ namespace skyline::loader {
return std::vector<u8>();
}
virtual void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) = 0;
/**
* @return Entry point to the start of the main executable in the ROM
*/
virtual void* LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) = 0;
};
}

View File

@ -12,7 +12,7 @@ namespace skyline::loader {
throw exception("Only NCAs with an ExeFS can be loaded directly");
}
void NcaLoader::LoadExeFs(const std::shared_ptr<vfs::FileSystem> &exeFs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
void* NcaLoader::LoadExeFs(const std::shared_ptr<vfs::FileSystem> &exeFs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
if (exeFs == nullptr)
throw exception("Cannot load a null ExeFS");
@ -25,6 +25,7 @@ namespace skyline::loader {
auto loadInfo{NsoLoader::LoadNso(nsoFile, process, state)};
u64 offset{loadInfo.size};
u8* base{loadInfo.base};
void* entry{loadInfo.entry};
state.logger->Info("Loaded nso 'rtld' at 0x{:X}", base);
@ -40,9 +41,11 @@ namespace skyline::loader {
}
state.process->memory.InitializeRegions(base, offset);
return entry;
}
void NcaLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
LoadExeFs(nca.exeFs, process, state);
void* NcaLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
return LoadExeFs(nca.exeFs, process, state);
}
}

View File

@ -23,8 +23,8 @@ namespace skyline::loader {
* @param exefs A filesystem object containing the ExeFS filesystem to load into memory
* @param process The process to load the ExeFS into
*/
static void LoadExeFs(const std::shared_ptr<vfs::FileSystem> &exefs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
static void* LoadExeFs(const std::shared_ptr<vfs::FileSystem> &exefs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
void* LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
};
}

View File

@ -44,7 +44,7 @@ namespace skyline::loader {
return buffer;
}
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
void* NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
Executable nroExecutable{};
nroExecutable.text.contents = GetSegment(header.text);
@ -61,5 +61,7 @@ namespace skyline::loader {
state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit);
auto loadInfo{LoadExecutable(process, state, nroExecutable)};
state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size);
return loadInfo.entry;
}
}

View File

@ -72,6 +72,6 @@ namespace skyline::loader {
std::vector<u8> GetIcon();
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
void* LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
};
}

View File

@ -54,9 +54,10 @@ namespace skyline::loader {
return LoadExecutable(process, state, nsoExecutable, offset);
}
void NsoLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
void* NsoLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit);
auto loadInfo{LoadNso(backing, process, state)};
state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size);
return loadInfo.entry;
}
}

View File

@ -83,6 +83,6 @@ namespace skyline::loader {
*/
static ExecutableLoadInfo LoadNso(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset = 0);
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
void* LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
};
}

View File

@ -34,8 +34,8 @@ namespace skyline::loader {
nacp = std::make_shared<vfs::NACP>(controlRomFs->OpenFile("control.nacp"));
}
void NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
NcaLoader::LoadExeFs(programNca->exeFs, process, state);
void* NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
return NcaLoader::LoadExeFs(programNca->exeFs, process, state);
}
std::vector<u8> NspLoader::GetIcon() {

View File

@ -26,6 +26,6 @@ namespace skyline::loader {
std::vector<u8> GetIcon();
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
void* LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
};
}

View File

@ -30,9 +30,9 @@ namespace skyline::kernel {
throw exception("Unsupported ROM extension.");
process = std::make_shared<kernel::type::KProcess>(state);
state.loader->LoadProcessData(process, state);
auto entry{state.loader->LoadProcessData(process, state)};
process->InitializeHeap();
process->CreateThread(reinterpret_cast<void*>(constant::BaseAddress))->Start();
process->CreateThread(entry)->Start();
state.nce->Execute();
}

View File

@ -59,7 +59,6 @@ namespace skyline::service::fssrv {
manager.RegisterService(std::make_shared<IFileSystem>(std::make_shared<vfs::OsFileSystem>(state.os->appFilesPath + "/switch" + saveDataPath), state, manager), session, response);
return {};
}
Result IFileSystemProxy::OpenDataStorageByCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {