Implement KTransferMemory correctly

This commit is contained in:
TheASVigilante 2023-04-16 19:31:49 +02:00
parent dac180d7c1
commit 93fd0b2536
8 changed files with 174 additions and 73 deletions

View File

@ -174,7 +174,9 @@ add_library(skyline SHARED
${source_DIR}/skyline/kernel/svc.cpp
${source_DIR}/skyline/kernel/types/KProcess.cpp
${source_DIR}/skyline/kernel/types/KThread.cpp
${source_DIR}/skyline/kernel/types/KTransferMemory.cpp
${source_DIR}/skyline/kernel/types/KSharedMemory.cpp
${source_DIR}/skyline/kernel/types/KMemory.cpp
${source_DIR}/skyline/kernel/types/KSyncObject.cpp
${source_DIR}/skyline/audio.cpp
${source_DIR}/skyline/gpu.cpp

View File

@ -577,8 +577,11 @@ namespace skyline::kernel::svc {
return;
}
auto tmem{state.process->NewHandle<kernel::type::KTransferMemory>(address, size, permission)};
state.process->memory.AddRef(tmem.item);
auto tmem{state.process->NewHandle<kernel::type::KTransferMemory>(size)};
if (!tmem.item->Map(span<u8>{address, size}, permission)) [[unlikely]] {
state.ctx->gpr.w0 = result::InvalidState;
return;
}
Logger::Debug("Creating transfer memory (0x{:X}) at 0x{:X} - 0x{:X} (0x{:X} bytes) ({}{}{})", tmem.handle, address, address + size, size, permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');

View File

@ -0,0 +1,56 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/sharedmem.h>
#include <unistd.h>
#include <asm/unistd.h>
#include "KMemory.h"
#include "KProcess.h"
namespace skyline::kernel::type {
KMemory::KMemory(const DeviceState &state, KType objectType, size_t size) : KObject(state, objectType), guest() {
fd = ASharedMemory_create(objectType == KType::KSharedMemory ? "HOS-KSharedMemory" : "HOS-KTransferMemory", size);
if (fd < 0) [[unlikely]]
throw exception("An error occurred while creating shared memory: {}", fd);
u8 *hostPtr{static_cast<u8 *>(mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0))};
if (hostPtr == MAP_FAILED) [[unlikely]]
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
host = span<u8>{hostPtr, size};
}
u8 *KMemory::Map(span<u8> map, memory::Permission permission) {
if (!state.process->memory.AddressSpaceContains(map)) [[unlikely]]
throw exception("KMemory 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())) [[unlikely]]
throw exception("KMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size());
if (guest.valid()) [[unlikely]]
throw exception("Mapping KMemory multiple times on guest is not supported: Requested Mapping: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size());
if (mmap(map.data(), map.size(), permission.Get() ? PROT_READ | PROT_WRITE | PROT_EXEC : PROT_NONE, MAP_SHARED | (map.data() ? MAP_FIXED : 0), fd, 0) == MAP_FAILED) [[unlikely]]
throw exception("An error occurred while mapping shared memory in guest: {}", strerror(errno));
guest = map;
return guest.data();
}
void KMemory::Unmap(span<u8> map) {
if (!state.process->memory.AddressSpaceContains(map)) [[unlikely]]
throw exception("KMemory 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())) [[unlikely]]
throw exception("KMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} ({} bytes)", map.data(), map.end().base(), map.size());
if (guest.data() != map.data() && guest.size() != map.size()) [[unlikely]]
throw exception("Unmapping KMemory partially is not supported: Requested Unmap: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size());
if (mmap(map.data(), map.size(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) [[unlikely]]
throw exception("An error occurred while unmapping shared/transfer memory in guest: {}", strerror(errno));
}
KMemory::~KMemory() {
if (host.valid())
munmap(host.data(), host.size());
close(fd);
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
@ -8,17 +8,31 @@
namespace skyline::kernel::type {
/**
* @brief The base kernel memory object that other memory classes derieve from
* @brief The base kernel shared memory object that other memory classes derieve from
*/
class KMemory : public KObject {
private:
int fd; //!< A file descriptor to the underlying shared memory
public:
KMemory(const DeviceState &state, KType objectType, span <u8> guest) : KObject(state, objectType), guest(guest) {}
KMemory(const DeviceState &state, KType objectType, size_t size);
/**
* @return A span representing the memory object on the guest
*/
span<u8> guest;
span<u8> host; //!< We also keep a host mirror of the underlying shared memory for host access, it is persistently mapped and should be used by anything accessing the memory on the host
virtual ~KMemory() = default;
/**
* @note 'ptr' needs to be in guest-reserved address space
*/
virtual u8 *Map(span<u8> map, memory::Permission permission);
/**
* @note 'ptr' needs to be in guest-reserved address space
*/
virtual void Unmap(span<u8> map);
virtual ~KMemory();
};
}

View File

@ -1,78 +1,34 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/sharedmem.h>
#include <unistd.h>
#include <asm/unistd.h>
#include "KSharedMemory.h"
#include "KProcess.h"
namespace skyline::kernel::type {
KSharedMemory::KSharedMemory(const DeviceState &state, size_t size, KType type)
: KMemory(state, type, span<u8>{}) {
fd = ASharedMemory_create(type == KType::KSharedMemory ? "HOS-KSharedMemory" : "HOS-KTransferMemory", size);
if (fd < 0) [[unlikely]]
throw exception("An error occurred while creating shared memory: {}", fd);
u8 *hostPtr{static_cast<u8 *>(mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0))};
if (hostPtr == MAP_FAILED) [[unlikely]]
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
host = span<u8>{hostPtr, size};
}
KSharedMemory::KSharedMemory(const DeviceState &state, size_t size)
: KMemory(state, KType::KSharedMemory, size) {}
u8 *KSharedMemory::Map(span<u8> map, memory::Permission permission) {
if (!state.process->memory.AddressSpaceContains(map)) [[unlikely]]
throw exception("KSharedMemory 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())) [[unlikely]]
throw exception("KSharedMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size());
if (guest.valid()) [[unlikely]]
throw exception("Mapping KSharedMemory multiple times on guest is not supported: Requested Mapping: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size());
u8 *result{KMemory::Map(map, permission)};
auto guestPtr{static_cast<u8 *>(mmap(map.data(), map.size(), permission.Get(), MAP_SHARED | (map.data() ? MAP_FIXED : 0), fd, 0))};
if (guestPtr == MAP_FAILED) [[unlikely]]
throw exception("An error occurred while mapping shared memory in guest: {}", strerror(errno));
guest = span<u8>{guestPtr, map.size()};
state.process->memory.MapSharedMemory(guest, permission);
if (objectType == KType::KTransferMemory) {
state.process->memory.MapTransferMemory(guest, permission);
state.process->memory.SetLockOnChunks(guest, true);
} else {
state.process->memory.MapSharedMemory(guest, permission);
}
return guest.data();
return result;
}
void KSharedMemory::Unmap(span<u8> map) {
auto &memoryManager{state.process->memory};
if (!memoryManager.AddressSpaceContains(map)) [[unlikely]]
throw exception("KSharedMemory 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())) [[unlikely]]
throw exception("KSharedMemory mapping isn't page-aligned: 0x{:X} - 0x{:X} ({} bytes)", map.data(), map.end().base(), map.size());
if (guest.data() != map.data() && guest.size() != map.size()) [[unlikely]]
throw exception("Unmapping KSharedMemory partially is not supported: Requested Unmap: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size());
if (mmap(map.data(), map.size(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) [[unlikely]]
throw exception("An error occurred while unmapping shared memory in guest: {}", strerror(errno));
KMemory::Unmap(map);
guest = span<u8>{};
memoryManager.UnmapMemory(map);
state.process->memory.UnmapMemory(map);
}
KSharedMemory::~KSharedMemory() {
if (state.process && guest.valid()) {
auto &memoryManager{state.process->memory};
if (mmap(guest.data(), guest.size(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) [[unlikely]]
Logger::Warn("An error occurred while unmapping shared memory: {}", strerror(errno));
state.process->memory.UnmapMemory(guest);
}
if (host.valid())
munmap(host.data(), host.size());
close(fd);
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
@ -10,13 +10,8 @@ namespace skyline::kernel::type {
* @brief KSharedMemory is used to retain two mappings of the same underlying memory, allowing sharing memory between two processes
*/
class KSharedMemory : public KMemory {
private:
int fd; //!< A file descriptor to the underlying shared memory
public:
span<u8> host; //!< We also keep a host mirror of the underlying shared memory for host access, it is persistently mapped and should be used by anything accessing the memory on the host
KSharedMemory(const DeviceState &state, size_t size, KType type = KType::KSharedMemory);
KSharedMemory(const DeviceState &state, size_t size);
/**
* @note 'ptr' needs to be in guest-reserved address space

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "KTransferMemory.h"
#include "KProcess.h"
namespace skyline::kernel::type {
KTransferMemory::KTransferMemory(const DeviceState &state, size_t size)
: KMemory(state, KType::KTransferMemory, size) {}
u8 *KTransferMemory::Map(span<u8> map, memory::Permission permission) {
std::memcpy(host.data(), map.data(), map.size());
u8 *result{KMemory::Map(map, permission)};
auto oldChunk{state.process->memory.GetChunk(map.data()).value()};
originalMapping = oldChunk.second;
if (!originalMapping.state.transferMemoryAllowed) [[unlikely]] {
Logger::Warn("Tried to map transfer memory with incompatible state at: 0x{:X} (0x{:X} bytes)", map.data(), map.size());
return nullptr;
} else {
state.process->memory.MapTransferMemory(guest, permission);
state.process->memory.SetLockOnChunks(guest, true);
return result;
}
}
void KTransferMemory::Unmap(span<u8> map) {
KMemory::Unmap(map);
guest = span<u8>{};
switch (originalMapping.state.type) {
case memory::MemoryType::CodeMutable:
state.process->memory.MapMutableCodeMemory(map);
break;
case memory::MemoryType::Heap:
state.process->memory.MapHeapMemory(map);
break;
default:
Logger::Warn("Unmapping KTransferMemory with incompatible state: (0x{:X})", originalMapping.state.value);
}
std::memcpy(map.data(), host.data(), map.size());
}
KTransferMemory::~KTransferMemory() {
if (state.process && guest.valid()) {
if (mmap(guest.data(), guest.size(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) [[unlikely]]
Logger::Warn("An error occurred while unmapping transfer memory in guest: {}", strerror(errno));
switch (originalMapping.state.type) {
case memory::MemoryType::CodeMutable:
state.process->memory.MapMutableCodeMemory(guest);
break;
case memory::MemoryType::Heap:
state.process->memory.MapHeapMemory(guest);
break;
default:
Logger::Warn("Unmapping KTransferMemory with incompatible state: (0x{:X})", originalMapping.state.value);
}
std::memcpy(guest.data(), host.data(), guest.size());
}
}
}

View File

@ -1,24 +1,35 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "KSharedMemory.h"
#include "KMemory.h"
namespace skyline::kernel::type {
/**
* @brief KTransferMemory is used to transfer memory from one application to another on HOS, we emulate this abstraction using KSharedMemory as it's essentially the same with the main difference being that KSharedMemory is allocated by the kernel while KTransferMemory is created from memory that's been allocated by the guest beforehand
* @note KSharedMemory::{Map, Unmap, ~KSharedMemory} contains code to handle differences in memory attributes and destruction
*/
class KTransferMemory : public KSharedMemory {
class KTransferMemory : public KMemory {
private:
ChunkDescriptor originalMapping;
public:
/**
* @note 'ptr' needs to be in guest-reserved address space
*/
KTransferMemory(const DeviceState &state, u8 *ptr, size_t size, memory::Permission permission)
: KSharedMemory(state, size, KType::KTransferMemory) {
std::memcpy(host.data(), ptr, size);
Map(span<u8>{ptr, size}, permission);
}
KTransferMemory(const DeviceState &state, size_t size);
/**
* @note 'ptr' needs to be in guest-reserved address space
*/
u8 *Map(span<u8> map, memory::Permission permission);
/**
* @note 'ptr' needs to be in guest-reserved address space
*/
void Unmap(span<u8> map);
~KTransferMemory();
};
}