Refactor the memory implementation and add Regions

This commit does a major refactor of the memory implementation, it forms a memory map which is far cleaner than trying to access it through a handle table lookup. In addition, it creates a common interface for all memory kernel objects: KMemory from which all other kernel memory objects inherit. This allows doing resizing, permission change, etc without casting to the base memory type.
This commit is contained in:
◱ PixelyIon 2020-01-21 12:46:57 +05:30 committed by ◱ PixelyIon
parent b13002f0e1
commit 00cdc1fd6f
30 changed files with 1273 additions and 632 deletions

View File

@ -13,12 +13,9 @@
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
<option name="FUNCTION_NON_TOP_AFTER_RETURN_TYPE_WRAP" value="0" />
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="0" />
<option name="FUNCTION_PARAMETERS_WRAP" value="5" />
<option name="FUNCTION_PARAMETERS_WRAP" value="0" />
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="5" />
<option name="SHIFT_OPERATION_WRAP" value="0" />
<option name="TEMPLATE_DECLARATION_STRUCT_WRAP" value="1" />
<option name="TEMPLATE_DECLARATION_FUNCTION_WRAP" value="1" />
<option name="TEMPLATE_CALL_ARGUMENTS_WRAP" value="5" />
<option name="TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE" value="true" />
<option name="CLASS_CONSTRUCTOR_INIT_LIST_WRAP" value="0" />
<option name="SUPERCLASS_LIST_WRAP" value="0" />
@ -79,8 +76,12 @@
<option name="BLANK_LINES_AROUND_CLASS" value="0" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="false" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="0" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="SOFT_MARGINS" value="140" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="LABEL_INDENT_SIZE" value="-2" />

View File

@ -36,6 +36,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/gpu/devices/nvhost_as_gpu.cpp
${source_DIR}/skyline/os.cpp
${source_DIR}/skyline/loader/nro.cpp
${source_DIR}/skyline/kernel/memory.cpp
${source_DIR}/skyline/kernel/ipc.cpp
${source_DIR}/skyline/kernel/svc.cpp
${source_DIR}/skyline/kernel/types/KProcess.cpp

View File

@ -23,11 +23,8 @@ namespace skyline {
namespace constant {
// Memory
constexpr u64 BaseAddr = 0x8000000; //!< The address space base
constexpr u64 MapAddr = BaseAddr + 0x80000000; //!< The address of the map region
constexpr u64 HeapAddr = MapAddr + 0x1000000000; //!< The address of the heap region
constexpr u64 BaseAddress = 0x8000000; //!< The address space base
constexpr u64 BaseEnd = 0x7FFFFFFFFF; //!< The end of the address space
constexpr u64 MapSize = 0x1000000000; //!< The size of the map region
constexpr u64 TotalPhyMem = 0xF8000000; // ~4 GB of RAM
constexpr size_t DefStackSize = 0x1E8480; //!< The default amount of stack: 2 MB
constexpr size_t HeapSizeDiv = 0x200000; //!< The amount heap size has to be divisible by
@ -38,8 +35,6 @@ namespace skyline {
constexpr u32 NroMagic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
// NCE
constexpr u8 NumRegs = 30; //!< The amount of registers that ARMv8 has
constexpr u16 SvcLast = 0x7F; //!< The index of the last SVC
constexpr u16 BrkRdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
constexpr u32 TpidrroEl0 = 0x5E83; //!< ID of TPIDRRO_EL0 in MRS
constexpr u32 CntfrqEl0 = 0x5F00; //!< ID of CNTFRQ_EL0 in MRS
constexpr u32 TegraX1Freq = 0x124F800; //!< The clock frequency of the Tegra X1 (19.2 MHz)
@ -53,7 +48,6 @@ namespace skyline {
constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch
constexpr u32 MtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex
constexpr u32 CheckInterval = 10000000; //!< The amount of cycles to wait between checking if the guest thread is dead
// IPC
constexpr size_t TlsIpcSize = 0x100; //!< The size of the IPC command buffer in a TLS slot
constexpr u8 PortSize = 0x8; //!< The size of a port name string
@ -78,11 +72,14 @@ namespace skyline {
constexpr u32 ServiceNotReg = 0xE15; //!< "Service not registered"
constexpr u32 InvSize = 0xCA01; //!< "Invalid size"
constexpr u32 InvAddress = 0xCC01; //!< "Invalid address"
constexpr u32 InvPermission = 0xE001; //!< "Invalid Permission"
constexpr u32 InvState = 0xD401; //!< "Invalid MemoryState"
constexpr u32 InvPermission = 0xD801; //!< "Invalid Permission"
constexpr u32 InvMemRange = 0xD801; //!< "Invalid Memory Range"
constexpr u32 InvPriority = 0xE001; //!< "Invalid Priority"
constexpr u32 InvHandle = 0xE401; //!< "Invalid handle"
constexpr u32 InvCombination = 0xE801; //!< "Invalid combination"
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
constexpr u32 Timeout = 0xEA01; //!< "Timeout"
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
constexpr u32 NotFound = 0xF201; //!< "Not found"
constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour"
}
@ -98,6 +95,51 @@ namespace skyline {
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
};
namespace utils {
/**
* @brief Returns the current time in nanoseconds
* @return The current time in nanoseconds
*/
inline u64 GetCurrTimeNs() {
return static_cast<u64>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
}
/**
* @brief Aligns up a value to a multiple of two
* @tparam Type The type of the values
* @param value The value to round up
* @param multiple The multiple to round up to (Should be a multiple of 2)
* @tparam TypeVal The type of the value
* @tparam TypeMul The type of the multiple
* @return The aligned value
*/
template <typename TypeVal, typename TypeMul>
inline TypeVal AlignUp(TypeVal value, TypeMul multiple) {
static_assert(std::is_integral<TypeVal>() && std::is_integral<TypeMul>());
multiple--;
return (value + multiple) & ~multiple;
}
/**
* @brief Aligns down a value to a multiple of two
* @param value The value to round down
* @param multiple The multiple to round down to (Should be a multiple of 2)
* @tparam TypeVal The type of the value
* @tparam TypeMul The type of the multiple
* @return The aligned value
*/
template <typename TypeVal, typename TypeMul>
inline TypeVal AlignDown(TypeVal value, TypeMul multiple) {
static_assert(std::is_integral<TypeVal>() && std::is_integral<TypeMul>());
multiple--;
return value & ~multiple;
}
inline bool PageAligned(u64 address) {
return !(address & (PAGE_SIZE - 1U));
}
}
/**
* @brief The Mutex class is a wrapper around an atomic bool used for synchronization
*/
@ -293,14 +335,6 @@ namespace skyline {
inline exception(const S &formatStr, Args &&... args) : runtime_error(fmt::format(formatStr, args...)) {}
};
/**
* @brief Returns the current time in nanoseconds
* @return The current time in nanoseconds
*/
inline u64 GetCurrTimeNs() {
return static_cast<u64>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
}
class NCE;
class JvmManager;
namespace gpu {

View File

@ -32,7 +32,7 @@ namespace skyline::kernel::ipc {
}
}
for (uint index = 0; header->Xno > index; index++) {
for (uint index = 0; header->xNo > index; index++) {
auto bufX = reinterpret_cast<BufferDescriptorX *>(currPtr);
if (bufX->Address()) {
inputBuf.emplace_back(bufX);
@ -41,7 +41,7 @@ namespace skyline::kernel::ipc {
currPtr += sizeof(BufferDescriptorX);
}
for (uint index = 0; header->Ano > index; index++) {
for (uint index = 0; header->aNo > index; index++) {
auto bufA = reinterpret_cast<BufferDescriptorABW *>(currPtr);
if (bufA->Address()) {
inputBuf.emplace_back(bufA);
@ -50,7 +50,7 @@ namespace skyline::kernel::ipc {
currPtr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->Bno > index; index++) {
for (uint index = 0; header->bNo > index; index++) {
auto bufB = reinterpret_cast<BufferDescriptorABW *>(currPtr);
if (bufB->Address()) {
outputBuf.emplace_back(bufB);
@ -59,7 +59,7 @@ namespace skyline::kernel::ipc {
currPtr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->Wno > index; index++) {
for (uint index = 0; header->wNo > index; index++) {
auto bufW = reinterpret_cast<BufferDescriptorABW *>(currPtr);
if (bufW->Address()) {
inputBuf.emplace_back(bufW, IpcBufferType::W);

View File

@ -23,10 +23,10 @@ namespace skyline::kernel::ipc {
*/
struct CommandHeader {
CommandType type : 16;
u8 Xno : 4;
u8 Ano : 4;
u8 Bno : 4;
u8 Wno : 4;
u8 xNo : 4;
u8 aNo : 4;
u8 bNo : 4;
u8 wNo : 4;
u32 rawSize : 10;
BufferCFlag cFlag : 4;
u32 : 17;

View File

@ -0,0 +1,158 @@
#include "memory.h"
#include "types/KProcess.h"
namespace skyline::kernel {
ChunkDescriptor *MemoryManager::GetChunk(u64 address) {
for (auto &chunk : chunkList)
if (chunk.address <= address && (chunk.address + chunk.size) >= address)
return &chunk;
return nullptr;
}
BlockDescriptor *MemoryManager::GetBlock(u64 address) {
auto chunk = GetChunk(address);
if (chunk)
for (auto &block : chunk->blockList)
if (block.address <= address && (block.address + block.size) >= address)
return &block;
return nullptr;
}
void MemoryManager::InsertChunk(const ChunkDescriptor &chunk) {
auto it = chunkList.begin();
if (chunkList.empty() || it->address > chunk.address)
chunkList.push_front(chunk);
else {
auto prevIt = it;
while (true) {
if (it == chunkList.end() || (prevIt->address < chunk.address && it->address > chunk.address)) {
if (prevIt->address + prevIt->size > chunk.address)
throw exception("InsertChunk: Descriptors are colliding: 0x{:X} and 0x{:X}", prevIt->address, chunk.address);
chunkList.insert_after(prevIt, chunk);
break;
}
prevIt = it++;
}
}
}
void MemoryManager::DeleteChunk(u64 address) {
chunkList.remove_if([address](const ChunkDescriptor &chunk) {
return chunk.address <= address && (chunk.address + chunk.size) >= address;
});
}
void MemoryManager::ResizeChunk(ChunkDescriptor *chunk, size_t size) {
if (std::next(chunk->blockList.begin()) == chunk->blockList.end())
chunk->blockList.begin()->size = size;
else if (size > chunk->size) {
auto end = chunk->blockList.begin();
for (; std::next(end) != chunk->blockList.end(); end++);
auto baseBlock = (*chunk->blockList.begin());
BlockDescriptor block{
.address = (end->address + end->size),
.size = (chunk->address + size) - (end->address + end->size),
.permission = baseBlock.permission,
.attributes = baseBlock.attributes,
};
chunk->blockList.insert_after(end, block);
} else if (chunk->size < size) {
auto endAddress = chunk->address + size;
chunk->blockList.remove_if([endAddress](const BlockDescriptor &block) {
return block.address > endAddress;
});
auto end = chunk->blockList.begin();
for (; std::next(end) != chunk->blockList.end(); end++);
end->size = endAddress - end->address;
}
chunk->size = size;
}
void MemoryManager::InsertBlock(ChunkDescriptor *chunk, BlockDescriptor block) {
for (auto iter = chunk->blockList.begin(); iter != chunk->blockList.end(); iter++) {
if (iter->address <= block.address && (iter->address + iter->size) >= block.address) {
if (iter->address == block.address && iter->size == block.size) {
iter->attributes = block.attributes;
iter->permission = block.permission;
} else {
auto endBlock = *iter;
endBlock.address = (block.address + block.size);
endBlock.size = (iter->address + iter->size) - endBlock.address;
iter->size = (iter->address - block.address);
chunk->blockList.insert_after(iter, {block, endBlock});
}
break;
}
}
}
void MemoryManager::InitializeRegions(u64 address, u64 size, const memory::AddressSpaceType type) {
switch(type) {
case memory::AddressSpaceType::AddressSpace32Bit:
throw exception("32-bit address spaces are not supported");
case memory::AddressSpaceType::AddressSpace36Bit: {
code.address = 0x8000000;
code.size = 0x78000000;
if(code.address > address || (code.size - (address - code.address)) < size)
throw exception("Code mapping larger than 36-bit code region");
alias.address = code.address + code.size;
alias.size = 0x180000000;
stack.address = alias.address;
stack.size = alias.size;
heap.address = alias.address + alias.size;
heap.size = 0x180000000;
tlsIo.address = heap.address + heap.size;
tlsIo.size = 0x1000000000;
break;
}
case memory::AddressSpaceType::AddressSpace39Bit: {
code.address = utils::AlignDown(address, 0x200000);
code.size = utils::AlignUp(address + size, 0x200000) - code.address;
alias.address = code.address + code.size;
alias.size = 0x1000000000;
heap.address = alias.address + alias.size;
heap.size = 0x180000000;
stack.address = heap.address + heap.size;
stack.size = 0x80000000;
tlsIo.address = stack.address + stack.size;
tlsIo.size = 0x1000000000;
break;
}
}
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
.address + heap.size, heap.size, stack.address, stack.address + stack.size, stack.size, tlsIo.address, tlsIo.address + tlsIo.size, tlsIo.size);
}
MemoryManager::MemoryManager(const DeviceState &state) : state(state) {}
std::optional<DescriptorPack> MemoryManager::Get(u64 address) {
auto chunk = GetChunk(address);
if (chunk)
for (auto &block : chunk->blockList)
if (block.address <= address && (block.address + block.size) >= address)
return DescriptorPack{block, *chunk};
return std::nullopt;
}
memory::Region MemoryManager::GetRegion(memory::Regions region) {
switch(region) {
case memory::Regions::Code:
return code;
case memory::Regions::Alias:
return alias;
case memory::Regions::Heap:
return heap;
case memory::Regions::Stack:
return stack;
case memory::Regions::TlsIo:
return tlsIo;
}
}
size_t MemoryManager::GetProgramSize() {
size_t size = 0;
for (const auto &chunk : chunkList)
size += chunk.size;
return size;
}
}

View File

@ -0,0 +1,357 @@
#pragma once
#include <common.h>
#include "types/KObject.h"
#include <forward_list>
namespace skyline {
namespace memory {
/**
* @brief The Permission struct holds the permission of a particular chunk of memory
*/
struct Permission {
/**
* @brief This constructor initializes all permissions to false
*/
Permission() {
r = 0;
w = 0;
x = 0;
};
/**
* @param read If memory has read permission
* @param write If memory has write permission
* @param execute If memory has execute permission
*/
Permission(bool read, bool write, bool execute) {
r = read;
w = write;
x = execute;
};
/**
* @brief Equality operator between two Permission objects
*/
inline bool operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); };
/**
* @brief Inequality operator between two Permission objects
*/
inline bool operator!=(const Permission &rhs) const { return !operator==(rhs); };
/**
* @return The value of the permission struct in Linux format
*/
int Get() const {
int perm = 0;
if (r)
perm |= PROT_READ;
if (w)
perm |= PROT_WRITE;
if (x)
perm |= PROT_EXEC;
return perm;
};
bool r; //!< The permission to read
bool w; //!< The permission to write
bool x; //!< The permission to execute
};
/**
* @brief This holds certain attributes of a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryAttribute
*/
union MemoryAttribute {
struct {
bool isBorrowed : 1; //!< This is required for async IPC user buffers
bool isIpcLocked : 1; //!< True when IpcRefCount > 0
bool isDeviceShared : 1; //!< True when DeviceRefCount > 0
bool isUncached : 1; //!< This is used to disable memory caching to share memory with the GPU
};
u32 value;
};
static_assert(sizeof(MemoryAttribute) == sizeof(u32));
/**
* @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
*/
struct MemoryInfo {
u64 address;
u64 size;
u32 type;
MemoryAttribute attributes;
union {
u32 _pad0_;
struct {
bool r : 1, w : 1, x : 1;
};
};
u32 ipcRefCount;
u32 deviceRefCount;
u32 : 32;
};
static_assert(sizeof(MemoryInfo) == 0x28);
/**
* @brief These are specific markers for the type of a memory region (https://switchbrew.org/wiki/SVC#MemoryType)
*/
enum class MemoryType : u8 {
Unmapped = 0x0,
Io = 0x1,
Normal = 0x2,
CodeStatic = 0x3,
CodeMutable = 0x4,
Heap = 0x5,
SharedMemory = 0x6,
Alias = 0x7,
ModuleCodeStatic = 0x8,
ModuleCodeMutable = 0x9,
Ipc = 0xA,
Stack = 0xB,
ThreadLocal = 0xC,
TransferMemoryIsolated = 0xD,
TransferMemory = 0xE,
ProcessMemory = 0xF,
Reserved = 0x10,
NonSecureIpc = 0x11,
NonDeviceIpc = 0x12,
KernelStack = 0x13,
CodeReadOnly = 0x14,
CodeWritable = 0x15
};
/**
* @brief This structure is used to hold the state of a certain block of memory (https://switchbrew.org/wiki/SVC#MemoryState)
*/
union MemoryState {
constexpr MemoryState(const u32 value) : value(value) {};
constexpr MemoryState() : value(0) {};
struct {
MemoryType type; //!< The MemoryType of this memory block
bool PermissionChangeAllowed : 1; //!< If the application can use svcSetMemoryPermission on this block
bool ForceReadWritableByDebugSyscalls : 1; //!< If the application can use svcWriteDebugProcessMemory on this block
bool IpcSendAllowed : 1; //!< If this block is allowed to be sent as an IPC buffer with flags=0
bool NonDeviceIpcSendAllowed : 1; //!< If this block is allowed to be sent as an IPC buffer with flags=3
bool NonSecureIpcSendAllowed : 1; //!< If this block is allowed to be sent as an IPC buffer with flags=1
bool _pad0_ : 1;
bool ProcessPermissionChangeAllowed : 1; //!< If the application can use svcSetProcessMemoryPermission on this block
bool MapAllowed : 1; //!< If the application can use svcMapMemory on this block
bool UnmapProcessCodeMemoryAllowed : 1; //!< If the application can use svcUnmapProcessCodeMemory on this block
bool TransferMemoryAllowed : 1; //!< If the application can use svcCreateTransferMemory on this block
bool QueryPhysicalAddressAllowed : 1; //!< If the application can use svcQueryPhysicalAddress on this block
bool MapDeviceAllowed : 1; //!< If the application can use svcMapDeviceAddressSpace or svcMapDeviceAddressSpaceByForce on this block
bool MapDeviceAlignedAllowed : 1; //!< If the application can use svcMapDeviceAddressSpaceAligned on this block
bool IpcBufferAllowed : 1; //!< If the application can use this block with svcSendSyncRequestWithUserBuffer
bool IsReferenceCounted : 1; //!< If the physical memory blocks backing this region are reference counted
bool MapProcessAllowed : 1; //!< If the application can use svcMapProcessMemory on this block
bool AttributeChangeAllowed : 1; //!< If the application can use svcSetMemoryAttribute on this block
bool CodeMemoryAllowed : 1; //!< If the application can use svcCreateCodeMemory on this block
};
u32 value;
};
static_assert(sizeof(MemoryState) == sizeof(u32));
/**
* @brief The preset states that different regions are set to (https://switchbrew.org/wiki/SVC#MemoryType)
*/
namespace MemoryStates {
constexpr MemoryState Unmapped = 0x00000000;
constexpr MemoryState Io = 0x00002001;
constexpr MemoryState CodeStatic = 0x00DC7E03;
constexpr MemoryState CodeMutable = 0x03FEBD04;
constexpr MemoryState Heap = 0x037EBD05;
constexpr MemoryState SharedMemory = 0x00402006;
constexpr MemoryState Alias = 0x00482907;
constexpr MemoryState AliasCode = 0x00DD7E08;
constexpr MemoryState AliasCodeData = 0x03FFBD09;
constexpr MemoryState Ipc = 0x005C3C0A;
constexpr MemoryState Stack = 0x005C3C0B;
constexpr MemoryState ThreadLocal = 0x0040200C;
constexpr MemoryState TransferMemoryIsolated = 0x015C3C0D;
constexpr MemoryState TransferMemory = 0x005C380E;
constexpr MemoryState SharedCode = 0x0040380F;
constexpr MemoryState Reserved = 0x00000010;
constexpr MemoryState NonSecureIpc = 0x005C3811;
constexpr MemoryState NonDeviceIpc = 0x004C2812;
constexpr MemoryState KernelStack = 0x00002013;
constexpr MemoryState CodeReadOnly = 0x00402214;
constexpr MemoryState CodeWritable = 0x00402015;
};
/**
* @brief This enumerates all of the memory regions in the process address space
*/
enum class Regions {
Code, //!< The code region contains all of the loaded in code
Alias, //!< The alias region is reserved for allocating thread stack before 2.0.0
Heap, //!< The heap region is reserved for heap allocations
Stack, //!< The stack region is reserved for allocating thread stack after 2.0.0
TlsIo, //!< The TLS/IO region is reserved for allocating TLS and Device MMIO
};
/**
* @brief This struct is used to hold the location and size of a memory region
*/
struct Region {
Regions id; //!< The ID of the region
u64 address; //!< The base address of the region
u64 size; //!< The size of the region in bytes
/**
* @brief Checks if the specified address is within the region
* @param address The address to check
* @return If the address is inside the region
*/inline bool IsInside(u64 address) {
return (this->address <= address) && ((this->address + this->size) > address);
}
};
/**
* @brief The type of the address space used by an application
*/
enum class AddressSpaceType {
AddressSpace32Bit, //!< 32-bit address space used by 32-bit applications
AddressSpace36Bit, //!< 36-bit address space used by 64-bit applications before 2.0.0
AddressSpace39Bit, //!< 39-bit address space used by 64-bit applications after 2.0.0
};
}
namespace loader {
class NroLoader;
}
namespace kernel {
namespace type {
class KPrivateMemory;
class KSharedMemory;
class KTransferMemory;
}
namespace svc {
void SetMemoryAttribute(DeviceState &state);
void MapMemory(DeviceState &state);
}
/**
* @brief This describes a single block of memory and all of it's individual attributes
*/
struct BlockDescriptor {
u64 address; //!< The address of the current block
u64 size; //!< The size of the current block in bytes
memory::Permission permission; //!< The permissions applied to the current block
memory::MemoryAttribute attributes; //!< The MemoryAttribute for the current block
};
/**
* @brief This describes a single chunk of memory, this is owned by a memory backing
*/
struct ChunkDescriptor {
u64 address; //!< The address of the current chunk
u64 size; //!< The size of the current chunk in bytes
memory::MemoryState state; //!< The MemoryState for the current block
std::forward_list<BlockDescriptor> blockList; //!< This linked list holds the block descriptors for all the children blocks of this Chunk
};
/**
* @brief This contains both of the descriptors for a specific address
*/
struct DescriptorPack {
const BlockDescriptor block; //!< The block descriptor at the address
const ChunkDescriptor chunk; //!< The chunk descriptor at the address
};
/**
* @brief The MemoryManager class handles the memory map and the memory regions of the process
*/
class MemoryManager {
private:
const DeviceState &state; //!< The state of the device
std::forward_list<ChunkDescriptor> chunkList; //!< This linked list holds all the chunk descriptors
memory::Region code{memory::Regions::Code}; //!< The Region object for the code memory region
memory::Region alias{memory::Regions::Alias}; //!< The Region object for the alias memory region
memory::Region heap{memory::Regions::Heap}; //!< The Region object for the heap memory region
memory::Region stack{memory::Regions::Stack}; //!< The Region object for the stack memory region
memory::Region tlsIo{memory::Regions::TlsIo}; //!< The Region object for the TLS/IO memory region
/**
* @param address The address to find a chunk at
* @return A pointer to the ChunkDescriptor or nullptr in case chunk was not found
*/
ChunkDescriptor *GetChunk(u64 address);
/**
* @param address The address to find a block at
* @return A pointer to the BlockDescriptor or nullptr in case chunk was not found
*/
BlockDescriptor *GetBlock(u64 address);
/**
* @brief Inserts a chunk into the memory map
* @param chunk The chunk to insert
*/
void InsertChunk(const ChunkDescriptor &chunk);
/**
* @brief Deletes a chunk located at the address from the memory map
* @param address The address of the chunk to delete
*/
void DeleteChunk(u64 address);
/**
* @brief Resize the specified chunk to the specified size
* @param chunk The chunk to resize
* @param size The new size of the chunk
*/
static void ResizeChunk(ChunkDescriptor *chunk, size_t size);
/**
* @brief Insert a block into a chunk
* @param chunk The chunk to insert the block into
* @param block The block to insert
*/
static void InsertBlock(ChunkDescriptor *chunk, BlockDescriptor block);
/**
* @brief This initializes all of the regions in the address space
* @param address The starting address of the code region
* @param size The size of the code region
* @param type The type of the address space
*/
void InitializeRegions(u64 address, u64 size, const memory::AddressSpaceType type);
public:
friend class type::KPrivateMemory;
friend class type::KSharedMemory;
friend class type::KTransferMemory;
friend class type::KProcess;
friend class loader::NroLoader;
friend void svc::SetMemoryAttribute(DeviceState &state);
friend void svc::MapMemory(skyline::DeviceState &state);
MemoryManager(const DeviceState &state);
/**
* @param address The address to query in the memory map
* @return A DescriptorPack retrieved from the memory map
*/
std::optional<DescriptorPack> Get(u64 address);
/**
* @param region The region to retrieve
* @return A Region object for the specified region
*/
memory::Region GetRegion(memory::Regions region);
/**
* @brief The total amount of space in bytes occupied by all memory mappings
* @return The cumulative size of all memory mappings in bytes
*/
size_t GetProgramSize();
};
}
}

View File

@ -10,109 +10,119 @@ namespace skyline::kernel::svc {
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
return;
}
std::shared_ptr<type::KPrivateMemory> heap;
try {
heap = state.process->memoryRegionMap.at(memory::Region::Heap);
heap->Resize(size, true);
} catch (const exception &) {
state.logger->Warn("svcSetHeapSize: Falling back to recreating memory");
state.process->UnmapPrivateRegion(memory::Region::Heap);
heap = state.process->MapPrivateRegion(constant::HeapAddr, size, {true, true, false}, memory::Type::Heap, memory::Region::Heap).item;
}
auto& heap = state.process->heap;
heap->Resize(size);
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.x1 = heap->address;
state.logger->Debug("svcSetHeapSize: Allocated at 0x{:X} for 0x{:X} bytes", heap->address, heap->size);
}
void SetMemoryAttribute(DeviceState &state) {
const u64 addr = state.ctx->registers.x0;
if ((addr & (PAGE_SIZE - 1U))) {
const u64 address = state.ctx->registers.x0;
if (!utils::PageAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcSetMemoryAttribute: 'address' not page aligned: {}", addr);
state.logger->Warn("svcSetMemoryAttribute: 'address' not page aligned: 0x{:X}", address);
return;
}
const u64 size = state.ctx->registers.x1;
if ((size & (PAGE_SIZE - 1U)) || !size) {
if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcSetMemoryAttribute: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
state.logger->Warn("svcSetMemoryAttribute: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
u32 mask = state.ctx->registers.w2;
u32 value = state.ctx->registers.w3;
u32 maskedValue = mask | value;
if (maskedValue != mask) {
memory::MemoryAttribute mask{.value = state.ctx->registers.w2};
memory::MemoryAttribute value{.value = state.ctx->registers.w3};
u32 maskedValue = mask.value | value.value;
if (maskedValue != mask.value || !mask.isUncached || mask.isDeviceShared || mask.isBorrowed || mask.isIpcLocked) {
state.ctx->registers.w0 = constant::status::InvCombination;
state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask, value);
state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask.value, value.value);
return;
}
memory::MemoryAttribute attribute = *reinterpret_cast<memory::MemoryAttribute *>(&maskedValue);
bool found = false;
for (const auto&[address, region] : state.process->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
bool subFound = false;
for (auto &subregion : region->regionInfoVec) {
if ((address >= subregion.address) && (address < (subregion.address + subregion.size)))
subregion.isUncached = attribute.isUncached;
subFound = true;
break;
}
if (!subFound)
region->regionInfoVec.emplace_back(addr, size, static_cast<bool>(attribute.isUncached));
found = true;
break;
}
}
if (!found) {
auto chunk = state.os->memory.GetChunk(address);
auto block = state.os->memory.GetBlock(address);
if (!chunk || !block) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", addr);
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", address);
return;
}
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} for 0x{:X} bytes", !attribute.isUncached, addr, size);
if(!chunk->state.AttributeChangeAllowed) {
state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", address);
return;
}
block->attributes.isUncached = value.isUncached;
MemoryManager::InsertBlock(chunk, *block);
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} for 0x{:X} bytes", !block->attributes.isUncached, address, size);
state.ctx->registers.w0 = constant::status::Success;
}
void MapMemory(DeviceState &state) {
const u64 destination = state.ctx->registers.x0;
const u64 source = state.ctx->registers.x1;
const u64 size = state.ctx->registers.x2;
if(!utils::PageAligned(destination) || !utils::PageAligned(source)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if(!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
if(!stack.IsInside(destination)) {
state.ctx->registers.w0 = constant::status::InvMemRange;
state.logger->Warn("svcMapMemory: Addresses not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
auto descriptor = state.os->memory.Get(source);
if(!descriptor) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if(!descriptor->chunk.state.MapAllowed) {
state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, descriptor->chunk.state.value);
return;
}
state.process->NewHandle<type::KPrivateMemory>(destination, size, memory::Permission{true, true, true}, memory::MemoryStates::Stack);
state.process->CopyMemory(source, destination, size);
auto object = state.process->GetMemoryObject(source);
if(!object)
throw exception("svcMapMemory: Cannot find memory object in handle table for address 0x{:X}", source);
object->UpdatePermission(source, size, {false, false, false});
state.logger->Debug("svcMapMemory: Mapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
state.ctx->registers.w0 = constant::status::Success;
}
void QueryMemory(DeviceState &state) {
u64 address = state.ctx->registers.x2;
memory::MemoryInfo memInfo{};
u64 addr = state.ctx->registers.x2 & ~(PAGE_SIZE - 1);
bool found = false;
for (const auto&[address, region] : state.process->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
memInfo = region->GetInfo(addr);
found = true;
break;
}
auto descriptor = state.os->memory.Get(address);
if (descriptor) {
memInfo = {
.address = descriptor->block.address,
.size = descriptor->block.size,
.type = static_cast<u32>(descriptor->chunk.state.type),
.attributes = descriptor->block.attributes,
.r = descriptor->block.permission.r,
.w = descriptor->block.permission.w,
.x = descriptor->block.permission.x,
.deviceRefCount = 0,
.ipcRefCount = 0,
};
} else {
memInfo = {
.address = constant::BaseEnd,
.size = ~(constant::BaseEnd - 1),
.type = static_cast<u32>(memory::MemoryType::Unmapped)
};
state.logger->Debug("svcQueryMemory: Cannot find block of address: 0x{:X}", address);
}
if (!found) {
for (const auto &object : state.process->handleTable) {
if (object.second->objectType == type::KType::KSharedMemory) {
const auto &mem = state.process->GetHandle<type::KSharedMemory>(object.first);
if (mem->guest.valid()) {
if (addr >= mem->guest.address && addr < (mem->guest.address + mem->guest.size)) {
memInfo = mem->GetInfo();
found = true;
break;
}
}
} else if (object.second->objectType == type::KType::KTransferMemory) {
const auto &mem = state.process->GetHandle<type::KTransferMemory>(object.first);
if (addr >= mem->cAddress && addr < (mem->cAddress + mem->cSize)) {
memInfo = mem->GetInfo();
found = true;
break;
}
}
}
if (!found) {
memInfo = {
.baseAddress = constant::BaseAddr,
.size = static_cast<u64>(constant::BaseEnd),
.type = static_cast<u64>(memory::Type::Unmapped)
};
state.logger->Debug("svcQueryMemory: Cannot find block of address: 0x{:X}", addr);
}
}
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", memInfo.baseAddress, memInfo.size, memInfo.type, static_cast<bool>(memInfo.memoryAttribute.isUncached), memInfo.r ? "R" : "-", memInfo.w ? "W" : "-", memInfo.x ? "X" : "-");
state.process->WriteMemory<memory::MemoryInfo>(memInfo, state.ctx->registers.x0);
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", memInfo.address, memInfo.size, memInfo.type, static_cast<bool>(memInfo.attributes.isUncached), memInfo.r ? "R" : "-", memInfo.w ? "W" : "-", memInfo.x ? "X" : "-");
state.process->WriteMemory(memInfo, state.ctx->registers.x0);
state.ctx->registers.w0 = constant::status::Success;
}
@ -122,17 +132,17 @@ namespace skyline::kernel::svc {
}
void CreateThread(DeviceState &state) {
u64 entryAddr = state.ctx->registers.x1;
u64 entryArg = state.ctx->registers.x2;
u64 entryAddress = state.ctx->registers.x1;
u64 entryArgument = state.ctx->registers.x2;
u64 stackTop = state.ctx->registers.x3;
u8 priority = static_cast<u8>(state.ctx->registers.w4);
if ((priority < constant::PriorityNin.first) && (priority > constant::PriorityNin.second)) { // NOLINT(misc-redundant-expression)
if ((priority < constant::PriorityNin.first) || (priority > constant::PriorityNin.second)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
return;
}
auto thread = state.process->CreateThread(entryAddr, entryArg, stackTop, priority);
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, PID: {})", thread->handle, entryAddr, entryArg, stackTop, priority, thread->pid);
auto thread = state.process->CreateThread(entryAddress, entryArgument, stackTop, priority);
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, PID: {})", thread->handle, entryAddress, entryArgument, stackTop, priority, thread->pid);
state.ctx->registers.w1 = thread->handle;
state.ctx->registers.w0 = constant::status::Success;
}
@ -201,16 +211,16 @@ namespace skyline::kernel::svc {
void MapSharedMemory(DeviceState &state) {
try {
auto object = state.process->GetHandle<type::KSharedMemory>(state.ctx->registers.w0);
u64 addr = state.ctx->registers.x1;
if ((addr & (PAGE_SIZE - 1U))) {
u64 address = state.ctx->registers.x1;
if (!utils::PageAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapSharedMemory: 'address' not page aligned: 0x{:X}", addr);
state.logger->Warn("svcMapSharedMemory: 'address' not page aligned: 0x{:X}", address);
return;
}
const u64 size = state.ctx->registers.x2;
if ((size & (PAGE_SIZE - 1U)) || !size) {
if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcMapSharedMemory: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
state.logger->Warn("svcMapSharedMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
u32 perm = state.ctx->registers.w3;
@ -220,8 +230,8 @@ namespace skyline::kernel::svc {
state.ctx->registers.w0 = constant::status::InvPermission;
return;
}
state.logger->Debug("svcMapSharedMemory: Mapping shared memory at 0x{:X} for {} bytes ({}{}{})", addr, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
object->Map(addr, size, permission);
state.logger->Debug("svcMapSharedMemory: Mapping shared memory at 0x{:X} for {} bytes ({}{}{})", address, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
object->Map(address, size, permission);
state.ctx->registers.w0 = constant::status::Success;
} catch (const std::exception &) {
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
@ -230,16 +240,16 @@ namespace skyline::kernel::svc {
}
void CreateTransferMemory(DeviceState &state) {
u64 addr = state.ctx->registers.x1;
if ((addr & (PAGE_SIZE - 1U))) {
u64 address = state.ctx->registers.x1;
if (!utils::PageAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcCreateTransferMemory: 'address' not page aligned: {}", addr);
state.logger->Warn("svcCreateTransferMemory: 'address' not page aligned: 0x{:X}", address);
return;
}
u64 size = state.ctx->registers.x2;
if ((size & (PAGE_SIZE - 1U)) || !size) {
if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcCreateTransferMemory: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
state.logger->Warn("svcCreateTransferMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
u32 perm = state.ctx->registers.w3;
@ -249,8 +259,8 @@ namespace skyline::kernel::svc {
state.ctx->registers.w0 = constant::status::InvPermission;
return;
}
state.logger->Debug("svcCreateTransferMemory: Creating transfer memory at 0x{:X} for {} bytes ({}{}{})", addr, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
auto shmem = state.process->NewHandle<type::KTransferMemory>(state.process->pid, addr, size, permission);
state.logger->Debug("svcCreateTransferMemory: Creating transfer memory at 0x{:X} for {} bytes ({}{}{})", address, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
auto shmem = state.process->NewHandle<type::KTransferMemory>(state.process->pid, address, size, permission);
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.w1 = shmem.handle;
}
@ -258,7 +268,7 @@ namespace skyline::kernel::svc {
void CloseHandle(DeviceState &state) {
auto handle = static_cast<handle_t>(state.ctx->registers.w0);
try {
state.process->handleTable.erase(handle);
state.process->handles.erase(handle);
state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::Success;
} catch (const std::exception &) {
@ -270,7 +280,7 @@ namespace skyline::kernel::svc {
void ResetSignal(DeviceState &state) {
auto handle = state.ctx->registers.w0;
try {
auto &object = state.process->handleTable.at(handle);
auto &object = state.process->handles.at(handle);
switch (object->objectType) {
case (type::KType::KEvent):
std::static_pointer_cast<type::KEvent>(object)->ResetSignal();
@ -305,7 +315,7 @@ namespace skyline::kernel::svc {
std::string handleStr;
for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle);
auto object = state.process->handleTable.at(handle);
auto object = state.process->handles.at(handle);
switch (object->objectType) {
case type::KType::KProcess:
case type::KType::KThread:
@ -321,7 +331,7 @@ namespace skyline::kernel::svc {
}
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
auto start = GetCurrTimeNs();
auto start = utils::GetCurrTimeNs();
while (true) {
uint index{};
for (const auto &object : objectTable) {
@ -333,7 +343,7 @@ namespace skyline::kernel::svc {
}
index++;
}
if ((GetCurrTimeNs() - start) >= timeout) {
if ((utils::GetCurrTimeNs() - start) >= timeout) {
state.logger->Debug("svcWaitSynchronization: Wait has timed out");
state.ctx->registers.w0 = constant::status::Timeout;
return;
@ -345,7 +355,7 @@ namespace skyline::kernel::svc {
auto addr = state.ctx->registers.x1;
if ((addr & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: {}", addr);
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", addr);
return;
}
auto handle = state.ctx->registers.w2;
@ -357,34 +367,34 @@ namespace skyline::kernel::svc {
}
void ArbitrateUnlock(DeviceState &state) {
auto addr = state.ctx->registers.x0;
if ((addr & ((1UL << WORD_BIT) - 1U))) {
auto address = state.ctx->registers.x0;
if ((address & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: {}", addr);
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address);
return;
}
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", addr);
state.process->MutexUnlock(addr);
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address);
state.process->MutexUnlock(address);
state.ctx->registers.w0 = constant::status::Success;
}
void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddr = state.ctx->registers.x0;
auto condAddr = state.ctx->registers.x1;
auto mtxAddress = state.ctx->registers.x0;
auto condAddress = state.ctx->registers.x1;
try {
auto &cvar = state.process->condVarMap.at(condAddr);
if ((mtxAddr & ((1UL << WORD_BIT) - 1U))) {
auto &cvar = state.process->condVars.at(condAddress);
if ((mtxAddress & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: {}", mtxAddr);
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress);
return;
}
auto handle = state.ctx->registers.w2;
if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Called from another thread");
state.process->MutexLock(mtxAddr);
auto &mutex = state.process->mutexMap.at(mtxAddr);
state.process->MutexLock(mtxAddress);
auto &mutex = state.process->mutexes.at(mtxAddress);
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x:{:X}, Timeout: {} ns", mtxAddr, condAddr, timeout);
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x:{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout);
timespec spec{};
clock_gettime(CLOCK_REALTIME, &spec);
u128 time = u128(spec.tv_sec * 1000000000U + spec.tv_nsec) + timeout; // u128 to prevent overflow
@ -394,10 +404,10 @@ namespace skyline::kernel::svc {
state.ctx->registers.w0 = constant::status::Timeout;
else
state.ctx->registers.w0 = constant::status::Success;
state.process->MutexUnlock(mtxAddr);
state.process->MutexUnlock(mtxAddress);
} catch (const std::out_of_range &) {
state.logger->Debug("svcWaitProcessWideKeyAtomic: No Conditional-Variable at 0x{:X}", condAddr);
state.process->condVarMap[condAddr] = PTHREAD_COND_INITIALIZER;
state.logger->Debug("svcWaitProcessWideKeyAtomic: No Conditional-Variable at 0x{:X}", condAddress);
state.process->condVars[condAddress] = PTHREAD_COND_INITIALIZER;
state.ctx->registers.w0 = constant::status::Success;
}
}
@ -407,15 +417,15 @@ namespace skyline::kernel::svc {
auto count = state.ctx->registers.w1;
try {
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count);
auto &cvar = state.process->condVarMap.at(address);
if(count == UINT32_MAX)
auto &cvar = state.process->condVars.at(address);
if (count == UINT32_MAX)
pthread_cond_broadcast(&cvar);
else
for (u32 iter = 0; iter < count; iter++)
pthread_cond_signal(&cvar);
} catch (const std::out_of_range &) {
state.logger->Debug("svcSignalProcessWideKey: No Conditional-Variable at 0x{:X}", address);
state.process->condVarMap[address] = PTHREAD_COND_INITIALIZER;
state.process->condVars[address] = PTHREAD_COND_INITIALIZER;
}
state.ctx->registers.w0 = constant::status::Success;
}
@ -488,46 +498,46 @@ namespace skyline::kernel::svc {
case constant::infoState::PrivilegedProcessId:
break;
case constant::infoState::AliasRegionBaseAddr:
out = constant::MapAddr;
out = state.os->memory.GetRegion(memory::Regions::Alias).address;
break;
case constant::infoState::AliasRegionSize:
out = constant::MapSize;
out = state.os->memory.GetRegion(memory::Regions::Alias).size;
break;
case constant::infoState::HeapRegionBaseAddr:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address;
out = state.os->memory.GetRegion(memory::Regions::Heap).address;
break;
case constant::infoState::HeapRegionSize:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->size;
out = state.os->memory.GetRegion(memory::Regions::Heap).size;
break;
case constant::infoState::TotalMemoryAvailable:
out = constant::TotalPhyMem;
break;
case constant::infoState::TotalMemoryUsage:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address + state.process->mainThreadStackSz + state.process->GetProgramSize();
out = state.process->heap->address + constant::DefStackSize + state.os->memory.GetProgramSize();
break;
case constant::infoState::AddressSpaceBaseAddr:
out = constant::BaseAddr;
out = constant::BaseAddress;
break;
case constant::infoState::AddressSpaceSize:
out = constant::BaseEnd;
break;
case constant::infoState::StackRegionBaseAddr:
out = state.thread->stackTop;
out = state.os->memory.GetRegion(memory::Regions::Stack).address;
break;
case constant::infoState::StackRegionSize:
out = state.process->mainThreadStackSz;
out = state.os->memory.GetRegion(memory::Regions::Stack).size;
break;
case constant::infoState::PersonalMmHeapSize:
out = constant::TotalPhyMem;
break;
case constant::infoState::PersonalMmHeapUsage:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address + state.process->mainThreadStackSz;
out = state.process->heap->address + constant::DefStackSize;
break;
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
out = constant::TotalPhyMem; // TODO: NPDM specifies SystemResourceSize, subtract that from this
break;
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address + state.process->mainThreadStackSz; // TODO: Same as above
out = state.process->heap->size + constant::DefStackSize; // TODO: Same as above
break;
case constant::infoState::UserExceptionContextAddr:
out = state.process->tlsPages[0]->Get(0);

View File

@ -37,7 +37,7 @@ namespace skyline {
};
namespace kernel::svc {
/**
* @brief Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
* @brief Sets the process heap to a given Size. It can both extend and shrink the heap. (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
*/
void SetHeapSize(DeviceState &state);
@ -46,6 +46,11 @@ namespace skyline {
*/
void SetMemoryAttribute(DeviceState &state);
/**
* @brief Maps a memory range into a different range. Mainly used for adding guard pages around stack. (https://switchbrew.org/wiki/SVC#svcSetMemoryAttribute)
*/
void MapMemory(DeviceState &state);
/**
* @brief Query information about an address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
*/
@ -169,7 +174,7 @@ namespace skyline {
SetHeapSize, // 0x01
nullptr, // 0x02
SetMemoryAttribute, // 0x03
nullptr, // 0x04
MapMemory, // 0x04
nullptr, // 0x05
QueryMemory, // 0x06
ExitProcess, // 0x07

View File

@ -0,0 +1,39 @@
#pragma once
#include <kernel/memory.h>
#include "KObject.h"
namespace skyline::kernel::type {
class KMemory : public KObject {
public:
KMemory(const DeviceState &state, KType objectType) : KObject(state, objectType) {}
/**
* @brief Remap a chunk of memory as to change the size occupied by it
* @param size The new size of the memory
* @return The address the memory was remapped to
*/
virtual void Resize(size_t size) = 0;
/**
* @brief Updates the permissions of a block of mapped memory
* @param address The starting address to change the permissions at
* @param size The size of the partition to change the permissions of
* @param permission The new permissions to be set for the memory
*/
virtual void UpdatePermission(const u64 address, const u64 size, memory::Permission permission) = 0;
/**
* @brief Updates the permissions of a chunk of mapped memory
* @param permission The new permissions to be set for the memory
*/
inline virtual void UpdatePermission(memory::Permission permission) = 0;
/**
* @brief Checks if the specified address is within the memory object
* @param address The address to check
* @return If the address is inside the memory object
*/
inline virtual bool IsInside(u64 address) = 0;
};
}

View File

@ -1,68 +1,66 @@
#include "KPrivateMemory.h"
#include "KProcess.h"
#include <nce.h>
#include <os.h>
#include <asm/unistd.h>
namespace skyline::kernel::type {
KPrivateMemory::KPrivateMemory(const DeviceState &state, u64 dstAddress, size_t size, memory::Permission permission, const memory::Type type, std::shared_ptr<KThread> thread) : state(state), address(dstAddress), size(size), permission(permission), type(type), KObject(state, KType::KPrivateMemory) {
Registers fregs{};
fregs.x0 = dstAddress;
fregs.x1 = size;
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x3 = static_cast<u64>(MAP_PRIVATE | MAP_ANONYMOUS | ((dstAddress) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, thread);
if (fregs.x0 < 0)
throw exception("An error occurred while mapping private region in child process");
if (!this->address)
this->address = fregs.x0;
}
u64 KPrivateMemory::Resize(size_t newSize, bool canMove) {
KPrivateMemory::KPrivateMemory(const DeviceState &state, u64 address, size_t size, memory::Permission permission, const memory::MemoryState memState) : state(state), address(address), size(size), KMemory(state, KType::KPrivateMemory) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = newSize;
fregs.x3 = canMove ? MREMAP_MAYMOVE : 0;
fregs.x8 = __NR_mremap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread);
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x3 = static_cast<u64>(MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while remapping private region in child process");
address = fregs.x0;
size = newSize;
return address;
throw exception("An error occurred while mapping private section in child process");
if (!this->address)
this->address = fregs.x0;
BlockDescriptor block{
.address = fregs.x0,
.size = size,
.permission = permission,
};
ChunkDescriptor chunk{
.address = fregs.x0,
.size = size,
.state = memState,
.blockList = {block},
};
state.os->memory.InsertChunk(chunk);
}
void KPrivateMemory::UpdatePermission(memory::Permission permission) {
void KPrivateMemory::Resize(size_t nSize) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = nSize;
fregs.x8 = __NR_mremap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while remapping private section in child process");
size = nSize;
auto chunk = state.os->memory.GetChunk(address);
MemoryManager::ResizeChunk(chunk, size);
}
void KPrivateMemory::UpdatePermission(const u64 address, const u64 size, memory::Permission permission) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x8 = __NR_mprotect;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while updating private region's permissions in child process");
this->permission = permission;
}
memory::MemoryInfo KPrivateMemory::GetInfo(u64 address) {
memory::MemoryInfo info{};
info.baseAddress = address;
info.size = size;
info.type = static_cast<u32>(type);
for (const auto &region : regionInfoVec)
if ((address >= region.address) && (address < (region.address + region.size))) {
info.memoryAttribute.isUncached = region.isUncached;
}
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);
info.r = permission.r;
info.w = permission.w;
info.x = permission.x;
info.ipcRefCount = ipcRefCount;
info.deviceRefCount = deviceRefCount;
return info;
throw exception("An error occurred while updating private section's permissions in child process");
auto chunk = state.os->memory.GetChunk(address);
BlockDescriptor block{
.address = address,
.size = size,
.permission = permission,
};
MemoryManager::InsertBlock(chunk, block);
}
KPrivateMemory::~KPrivateMemory() {
@ -72,9 +70,10 @@ namespace skyline::kernel::type {
fregs.x0 = address;
fregs.x1 = size;
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.process->pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
}
} catch (const std::exception &) {
}
state.os->memory.DeleteChunk(address);
}
};

View File

@ -1,55 +1,59 @@
#pragma once
#include <memory.h>
#include "KObject.h"
#include "KMemory.h"
namespace skyline::kernel::type {
/**
* KPrivateMemory is used to hold some amount of private memory
* @brief KPrivateMemory is used to map memory local to the guest process
*/
class KPrivateMemory : public KObject {
class KPrivateMemory : public KMemory {
private:
const DeviceState &state; //!< The state of the device
public:
u64 address; //!< The address of the allocated memory
size_t size; //!< The size of the allocated memory
u16 ipcRefCount{}; //!< The amount of reference to this memory for IPC
u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC
memory::Permission permission; //!< The permissions for the allocated memory
const memory::Type type; //!< The type of this memory allocation
std::vector<memory::RegionInfo> regionInfoVec; //!< This holds information about specific memory regions
u64 address{}; //!< The address of the allocated memory
size_t size{}; //!< The size of the allocated memory
/**
* @param state The state of the device
* @param dstAddress The address to map to (If NULL then an arbitrary address is picked)
* @param address The address to map to (If NULL then an arbitrary address is picked)
* @param size The size of the allocation
* @param permission The permissions for the allocated memory
* @param type The type of the memory
* @param thread The thread to execute the calls on
* @param memState The MemoryState of the chunk of memory
*/
KPrivateMemory(const DeviceState &state, u64 dstAddress, size_t size, memory::Permission permission, const memory::Type type, std::shared_ptr<KThread> thread = 0);
KPrivateMemory(const DeviceState &state, u64 address, size_t size, memory::Permission permission, const memory::MemoryState memState);
/**
* @brief Remap a chunk of memory as to change the size occupied by it
* @param newSize The new size of the memory
* @param canMove If the memory can move if there is not enough space at the current address
* @param size The new size of the memory
* @return The address the memory was remapped to
*/
u64 Resize(size_t newSize, bool canMove);
virtual void Resize(size_t size);
/**
* @brief Updates the permissions of a block of mapped memory
* @param address The starting address to change the permissions at
* @param size The size of the partition to change the permissions of
* @param permission The new permissions to be set for the memory
*/
virtual void UpdatePermission(const u64 address, const u64 size, memory::Permission permission);
/**
* @brief Updates the permissions of a chunk of mapped memory
* @param permission The new permissions to be set for the memory
*/
void UpdatePermission(memory::Permission permission);
inline virtual void UpdatePermission(memory::Permission permission) {
UpdatePermission(address, size, permission);
}
/**
* @brief Returns a MemoryInfo object
* @param address The specific address being queried (Used to fill MemoryAttribute)
* @return A Memory::MemoryInfo struct based on attributes of the memory
* @brief Checks if the specified address is within the memory object
* @param address The address to check
* @return If the address is inside the memory object
*/
memory::MemoryInfo GetInfo(u64 address);
inline virtual bool IsInside(u64 address) {
return (this->address <= address) && ((this->address + this->size) > address);
}
/**
* @brief The destructor of private memory, it deallocates the memory

View File

@ -26,12 +26,15 @@ namespace skyline::kernel::type {
}
u64 KProcess::GetTlsSlot() {
for (auto &tlsPage: tlsPages) {
for (auto &tlsPage: tlsPages)
if (!tlsPage->Full())
return tlsPage->ReserveSlot();
}
auto tlsMem = NewHandle<KPrivateMemory>(0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal, threadMap.at(pid)).item;
memoryMap[tlsMem->address] = tlsMem;
u64 address;
if(tlsPages.empty())
address = state.os->memory.GetRegion(memory::Regions::TlsIo).address;
else
address = (*(tlsPages.end()-1))->address + PAGE_SIZE;
auto tlsMem = NewHandle<KPrivateMemory>(address, PAGE_SIZE, memory::Permission(true, true, false), memory::MemoryStates::ThreadLocal).item;
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
auto &tlsPage = tlsPages.back();
if (tlsPages.empty())
@ -39,13 +42,15 @@ namespace skyline::kernel::type {
return tlsPage->ReserveSlot();
}
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize, std::shared_ptr<type::KSharedMemory> &tlsMemory) : pid(pid), mainThreadStackSz(stackSize), KSyncObject(state, KType::KProcess) {
void KProcess::InitializeMemory() {
heap = NewHandle<KPrivateMemory>(state.os->memory.GetRegion(memory::Regions::Heap).address, constant::DefHeapSize, memory::Permission{true, true, false}, memory::MemoryStates::Heap).item;
threads[pid]->tls = GetTlsSlot();
}
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize, std::shared_ptr<type::KSharedMemory> &tlsMemory) : pid(pid), KSyncObject(state, KType::KProcess) {
auto thread = NewHandle<KThread>(pid, entryPoint, 0x0, stackBase + stackSize, 0, constant::DefaultPriority, this, tlsMemory).item;
// Remove GetTlsSlot from KThread ctor and cleanup ctor in general
threadMap[pid] = thread;
threads[pid] = thread;
state.nce->WaitThreadInit(thread);
thread->tls = GetTlsSlot();
MapPrivateRegion(constant::HeapAddr, constant::DefHeapSize, {true, true, false}, memory::Type::Heap, memory::Region::Heap);
memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC);
if (memFd == -1)
throw exception("Cannot open file descriptor to /proc/{}/mem, \"{}\"", pid, strerror(errno));
@ -78,24 +83,24 @@ namespace skyline::kernel::type {
fregs.regs[0] = entryPoint;
fregs.regs[1] = stackTop;
fregs.x8 = __NR_clone;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
auto pid = static_cast<pid_t>(fregs.regs[0]);
if (pid == -1)
throw exception("Cannot create thread: Address: 0x{:X}, Stack Top: 0x{:X}", entryPoint, stackTop);
auto process = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this).item;
threadMap[pid] = process;
threads[pid] = process;
return process;
*/
return nullptr;
}
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
struct iovec local {
struct iovec local{
.iov_base = destination,
.iov_len = size
};
struct iovec remote {
.iov_base = reinterpret_cast<void*>(offset),
struct iovec remote{
.iov_base = reinterpret_cast<void *>(offset),
.iov_len = size
};
@ -104,12 +109,12 @@ namespace skyline::kernel::type {
}
void KProcess::WriteMemory(void *source, u64 offset, size_t size) const {
struct iovec local {
struct iovec local{
.iov_base = source,
.iov_len = size
};
struct iovec remote {
.iov_base = reinterpret_cast<void*>(offset),
struct iovec remote{
.iov_base = reinterpret_cast<void *>(offset),
.iov_len = size
};
@ -117,43 +122,52 @@ namespace skyline::kernel::type {
pwrite64(memFd, source, size, offset);
}
KProcess::HandleOut<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region) {
auto mem = NewHandle<KPrivateMemory>(address, size, perms, type, threadMap.at(pid));
memoryMap[mem.item->address] = mem.item;
memoryRegionMap[region] = mem.item;
return mem;
void KProcess::CopyMemory(u64 source, u64 destination, size_t size) const {
if (size <= PAGE_SIZE) {
std::vector<u8> buffer(size);
state.process->ReadMemory(buffer.data(), source, size);
state.process->WriteMemory(buffer.data(), destination, size);
} else {
Registers fregs{};
fregs.x0 = source;
fregs.x1 = destination;
fregs.x2 = size;
state.nce->ExecuteFunction(ThreadCall::Memcopy, fregs);
}
}
bool KProcess::UnmapPrivateRegion(const skyline::memory::Region region) {
if (!memoryRegionMap.count(region))
return false;
memoryMap.erase(memoryRegionMap.at(region)->address);
memoryRegionMap.erase(region);
return true;
}
size_t KProcess::GetProgramSize() {
size_t sharedSize = 0;
for (auto &region : memoryRegionMap)
sharedSize += region.second->size;
return sharedSize;
std::shared_ptr<KMemory> KProcess::GetMemoryObject(u64 address) {
for(auto& [handle, object] : state.process->handles) {
switch(object->objectType) {
case type::KType::KPrivateMemory:
case type::KType::KSharedMemory:
case type::KType::KTransferMemory: {
auto mem = std::static_pointer_cast<type::KMemory>(object);
if (mem->IsInside(address))
return mem;
}
default:
break;
}
}
return nullptr;
}
void KProcess::MutexLock(u64 address) {
try {
auto mtx = mutexMap.at(address);
auto mtx = mutexes.at(address);
pthread_mutex_lock(&mtx);
u32 mtxVal = ReadMemory<u32>(address);
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | state.thread->handle;
WriteMemory(mtxVal, address);
} catch (const std::out_of_range &) {
mutexMap[address] = PTHREAD_MUTEX_INITIALIZER;
mutexes[address] = PTHREAD_MUTEX_INITIALIZER;
}
}
void KProcess::MutexUnlock(u64 address) {
try {
auto mtx = mutexMap.at(address);
auto mtx = mutexes.at(address);
u32 mtxVal = ReadMemory<u32>(address);
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
throw exception("A non-owner thread tried to release a mutex");
@ -161,7 +175,7 @@ namespace skyline::kernel::type {
WriteMemory(mtxVal, address);
pthread_mutex_unlock(&mtx);
} catch (const std::out_of_range &) {
mutexMap[address] = PTHREAD_MUTEX_INITIALIZER;
mutexes[address] = PTHREAD_MUTEX_INITIALIZER;
}
}
}

View File

@ -6,6 +6,7 @@
#include "KSharedMemory.h"
#include "KSession.h"
#include "KEvent.h"
#include <kernel/memory.h>
#include <condition_variable>
namespace skyline::kernel::type {
@ -57,25 +58,16 @@ namespace skyline::kernel::type {
*/
u64 GetTlsSlot();
public:
enum class Status {
Created, //!< The process was created but the main thread has not started yet
Started, //!< The process has been started
Exiting //!< The process is exiting
} status = Status::Created; //!< The state of the process
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
pid_t pid; //!< The PID of the main thread
size_t mainThreadStackSz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
int memFd; //!< The file descriptor to the memory of the process
std::unordered_map<u64, std::shared_ptr<KPrivateMemory>> memoryMap; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory, used to keep track of KPrivateMemory instances
std::unordered_map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
std::unordered_map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::unordered_map<pid_t, std::shared_ptr<KThread>> threadMap; //!< A mapping from a PID to it's corresponding KThread object
std::unordered_map<u64, pthread_mutex_t> mutexMap; //!< A map from a mutex's address to a vector of threads waiting on it
std::unordered_map<u64, pthread_cond_t> condVarMap; //!< A map from a conditional variable's address to a vector of threads waiting on it
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
/**
* This is used as the output for functions that return created kernel objects
* @brief This initializes heap and the initial TLS page
*/
void InitializeMemory();
public:
friend OS;
/**
* @brief This is used as the output for functions that return created kernel objects
* @tparam objectClass The class of the kernel object
*/
template<typename objectClass>
@ -84,6 +76,25 @@ namespace skyline::kernel::type {
handle_t handle; //!< The handle of the object in the process
};
/**
* @brief This enum is used to describe the current status of the process
*/
enum class Status {
Created, //!< The process was created but the main thread has not started yet
Started, //!< The process has been started
Exiting //!< The process is exiting
} status = Status::Created; //!< The state of the process
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
pid_t pid; //!< The PID of the main thread
int memFd; //!< The file descriptor to the memory of the process
std::unordered_map<handle_t, std::shared_ptr<KObject>> handles; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::unordered_map<pid_t, std::shared_ptr<KThread>> threads; //!< A mapping from a PID to it's corresponding KThread object
std::unordered_map<u64, pthread_mutex_t> mutexes; //!< A map from a mutex's address to a vector of threads waiting on it
std::unordered_map<u64, pthread_cond_t> condVars; //!< A map from a conditional variable's address to a vector of threads waiting on it
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
std::shared_ptr<KPrivateMemory> heap; //!< The kernel memory object backing the allocated heap
/**
* @brief Creates a KThread object for the main thread and opens the process's memory file
* @param state The state of the device
@ -151,27 +162,12 @@ namespace skyline::kernel::type {
void WriteMemory(void *source, u64 offset, size_t size) const;
/**
* @brief Map a chunk of process local memory (private memory)
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @param type The type of the memory
* @param region The specific region this memory is mapped for
* @return The HandleOut of the created KPrivateMemory
* @brief Copy one chunk to another in the process's memory
* @param source The address of where the data to read is present
* @param destination The address to write the read data to
* @param size The amount of memory to be copied
*/
HandleOut<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region);
/**
* @brief Unmap a chunk of process local memory (private memory)
* @param region The region of memory to unmap
* @return If the region was mapped at all
*/
bool UnmapPrivateRegion(const memory::Region region);
/**
* @brief Returns the total memory occupied by regions mapped for the process
*/
size_t GetProgramSize();
void CopyMemory(u64 source, u64 destination, size_t size) const;
/**
* @brief Creates a new handle to a KObject and adds it to the process handle_table
@ -186,18 +182,18 @@ namespace skyline::kernel::type {
item = std::make_shared<objectClass>(state, handleIndex, args...);
else
item = std::make_shared<objectClass>(state, args...);
handleTable[handleIndex] = std::static_pointer_cast<KObject>(item);
handles[handleIndex] = std::static_pointer_cast<KObject>(item);
return {item, handleIndex++};
}
/**
* @brief This inserts an item into the process handle table
* @brief Inserts an item into the process handle table
* @param item The item to insert
* @return The handle of the corresponding item in the handle table
*/
template<typename objectClass>
handle_t InsertItem(std::shared_ptr<objectClass> &item) {
handleTable[handleIndex] = std::static_pointer_cast<KObject>(item);
handles[handleIndex] = std::static_pointer_cast<KObject>(item);
return handleIndex++;
}
@ -227,7 +223,7 @@ namespace skyline::kernel::type {
else
throw exception("KProcess::GetHandle couldn't determine object type");
try {
auto item = handleTable.at(handle);
auto item = handles.at(handle);
if (item->objectType == objectType)
return std::static_pointer_cast<objectClass>(item);
else
@ -237,6 +233,21 @@ namespace skyline::kernel::type {
}
}
/**
* @brief Retrieves a kernel memory object that owns the specified address
* @param address The address to look for
* @return A shared pointer to the corresponding KMemory object
*/
std::shared_ptr<KMemory> GetMemoryObject(u64 address);
/**
* @brief This deletes a certain handle from the handle table
* @param handle The handle to delete
*/
inline void DeleteHandle(handle_t handle) {
handles.erase(handle);
}
/**
* @brief This locks the Mutex at the specified address
* @param address The address of the mutex

View File

@ -1,22 +1,22 @@
#include "KSharedMemory.h"
#include "KProcess.h"
#include <nce.h>
#include <os.h>
#include <android/sharedmem.h>
#include <unistd.h>
#include <asm/unistd.h>
namespace skyline::kernel::type {
KSharedMemory::KSharedMemory(const DeviceState &state, u64 address, size_t size, const memory::Permission permission, memory::Type type) : type(type), KObject(state, KType::KSharedMemory) {
KSharedMemory::KSharedMemory(const DeviceState &state, u64 address, size_t size, const memory::Permission permission, memory::MemoryState memState) : initialState(memState), KMemory(state, KType::KSharedMemory) {
fd = ASharedMemory_create("", size);
if (fd < 0)
throw exception("An error occurred while creating shared memory: {}", fd);
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0));
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | ((address) ? MAP_FIXED : 0), fd, 0));
if (address == reinterpret_cast<u64>(MAP_FAILED))
throw exception("An occurred while mapping shared region: {}", strerror(errno));
kernel = {address, size, permission};
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
kernel = {.address = address, .size = size, .permission = permission};
}
u64 KSharedMemory::Map(u64 address, u64 size, memory::Permission permission) {
u64 KSharedMemory::Map(const u64 address, const u64 size, memory::Permission permission) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
@ -24,10 +24,22 @@ namespace skyline::kernel::type {
fregs.x3 = static_cast<u64>(MAP_SHARED | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(fd);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while mapping shared region in child process");
guest = {fregs.x0, size, permission};
throw exception("An error occurred while mapping shared memory in guest");
guest = {.address = fregs.x0, .size = size, .permission = permission};
ChunkDescriptor chunk{
.address = fregs.x0,
.size = size,
.state = initialState,
};
BlockDescriptor block{
.address = fregs.x0,
.size = size,
.permission = permission,
};
chunk.blockList.push_front(block);
state.os->memory.InsertChunk(chunk);
return fregs.x0;
}
@ -38,52 +50,45 @@ namespace skyline::kernel::type {
fregs.x1 = guest.size;
fregs.x2 = size;
fregs.x8 = __NR_mremap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while remapping shared region in child process");
throw exception("An error occurred while remapping shared memory in guest");
guest.size = size;
auto chunk = state.os->memory.GetChunk(guest.address);
MemoryManager::ResizeChunk(chunk, size);
}
if (kernel.valid()) {
if (mremap(reinterpret_cast<void *>(kernel.address), kernel.size, size, 0) == MAP_FAILED)
throw exception("An occurred while remapping shared region: {}", strerror(errno));
throw exception("An error occurred while remapping shared region: {}", strerror(errno));
kernel.size = size;
}
}
void KSharedMemory::UpdatePermission(memory::Permission permission, bool host) {
void KSharedMemory::UpdatePermission(u64 address, u64 size, memory::Permission permission, bool host) {
if (guest.valid() && !host) {
Registers fregs{};
fregs.x0 = guest.address;
fregs.x1 = guest.size;
fregs.x2 = static_cast<u64>(guest.permission.Get());
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x8 = __NR_mprotect;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while updating shared region's permissions in child process");
guest.permission = permission;
throw exception("An error occurred while updating shared memory's permissions in guest");
auto chunk = state.os->memory.GetChunk(address);
BlockDescriptor block{
.address = address,
.size = size,
.permission = permission,
};
MemoryManager::InsertBlock(chunk, block);
}
if (kernel.valid() && host) {
if (mprotect(reinterpret_cast<void *>(kernel.address), kernel.size, permission.Get()) == reinterpret_cast<u64>(MAP_FAILED))
throw exception("An occurred while remapping shared region: {}", strerror(errno));
throw exception("An error occurred while remapping shared memory: {}", strerror(errno));
kernel.permission = permission;
}
}
memory::MemoryInfo KSharedMemory::GetInfo() {
memory::MemoryInfo info{};
info.baseAddress = guest.address;
info.size = guest.size;
info.type = static_cast<u32>(type);
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);
info.r = guest.permission.r;
info.w = guest.permission.w;
info.x = guest.permission.x;
info.ipcRefCount = ipcRefCount;
info.deviceRefCount = deviceRefCount;
return info;
}
KSharedMemory::~KSharedMemory() {
try {
if (guest.valid() && state.process) {
@ -91,12 +96,13 @@ namespace skyline::kernel::type {
fregs.x0 = guest.address;
fregs.x1 = guest.size;
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.process->pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
}
if (kernel.valid())
munmap(reinterpret_cast<void *>(kernel.address), kernel.size);
} catch (const std::exception &) {
}
state.os->memory.DeleteChunk(guest.address);
close(fd);
}
};

View File

@ -1,15 +1,15 @@
#pragma once
#include <memory.h>
#include "KObject.h"
#include "KMemory.h"
namespace skyline::kernel::type {
/**
* @brief KSharedMemory is used to hold a particular amount of shared memory
*/
class KSharedMemory : public KObject {
class KSharedMemory : public KMemory {
private:
int fd; //!< A file descriptor to the underlying shared memory
memory::MemoryState initialState; //!< This is to hold the initial state for the Map call
public:
/**
@ -27,21 +27,17 @@ namespace skyline::kernel::type {
inline bool valid() { return address && size && permission.Get(); }
} kernel, guest;
u16 ipcRefCount{}; //!< The amount of reference to this memory for IPC
u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC
memory::Type type; //!< The type of this memory allocation
/**
* @param state The state of the device
* @param address The address of the allocation on the kernel (Arbitrary is 0)
* @param address The address of the allocation on the kernel (If NULL then an arbitrary address is picked)
* @param size The size of the allocation on the kernel
* @param permission The permission of the kernel process
* @param type The type of the memory
* @param memState The MemoryState of the chunk of memory
*/
KSharedMemory(const DeviceState &state, u64 address, size_t size, const memory::Permission permission, memory::Type type);
KSharedMemory(const DeviceState &state, u64 address, size_t size, const memory::Permission permission, memory::MemoryState memState = memory::MemoryStates::SharedMemory);
/**
* @brief Maps the shared memory at an address in the guest
* @brief Maps the shared memory in the guest
* @param address The address to map to (If NULL an arbitrary address is picked)
* @param size The amount of shared memory to map
* @param permission The permission of the kernel process
@ -53,20 +49,44 @@ namespace skyline::kernel::type {
* @brief Resize a chunk of memory as to change the size occupied by it
* @param size The new size of the memory
*/
void Resize(size_t size);
virtual void Resize(size_t size);
/**
* @brief Updates the permissions of a block of mapped memory
* @param address The starting address to change the permissions at
* @param size The size of the partition to change the permissions of
* @param permission The new permissions to be set for the memory
* @param host Set the permissions for the kernel rather than the guest
*/
void UpdatePermission(u64 address, u64 size, memory::Permission permission, bool host = false);
/**
* @brief Updates the permissions of a block of mapped memory
* @param address The starting address to change the permissions at
* @param size The size of the partition to change the permissions of
* @param permission The new permissions to be set for the memory
*/
virtual void UpdatePermission(u64 address, u64 size, memory::Permission permission) {
UpdatePermission(address, size, permission, false);
}
/**
* @brief Updates the permissions of a chunk of mapped memory
* @param permission The new permissions to be set for the memory
* @param kernel Set the permissions for the kernel rather than the guest
*/
void UpdatePermission(memory::Permission permission, bool host = 0);
inline virtual void UpdatePermission(memory::Permission permission) {
UpdatePermission(guest.address, guest.size, permission, false);
}
/**
* @brief Creates a MemoryInfo struct from the current instance
* @return A Memory::MemoryInfo struct based on attributes of the memory
* @brief Checks if the specified address is within the guest memory object
* @param address The address to check
* @return If the address is inside the guest memory object
*/
memory::MemoryInfo GetInfo();
inline virtual bool IsInside(u64 address) {
return (guest.address <= address) && ((guest.address + guest.size) > address);
}
/**
* @brief The destructor of shared memory, it deallocates the memory from all processes

View File

@ -5,7 +5,7 @@
namespace skyline::kernel::type {
KThread::KThread(const DeviceState &state, handle_t handle, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent, std::shared_ptr<type::KSharedMemory> &tlsMemory) : handle(handle), pid(self_pid), entryPoint(entryPoint), entryArg(entryArg), stackTop(stackTop), tls(tls), priority(priority), parent(parent), ctxMemory(tlsMemory), KSyncObject(state,
KType::KThread) {
KType::KThread) {
UpdatePriority(priority);
}
@ -18,7 +18,7 @@ namespace skyline::kernel::type {
if (pid == parent->pid)
parent->status = KProcess::Status::Started;
status = Status::Running;
state.nce->StartThread(entryArg, handle, parent->threadMap.at(pid));
state.nce->StartThread(entryArg, handle, parent->threads.at(pid));
}
}

View File

@ -4,8 +4,25 @@
#include <asm/unistd.h>
namespace skyline::kernel::type {
KTransferMemory::KTransferMemory(const DeviceState &state, pid_t pid, u64 address, size_t size, const memory::Permission permission) : owner(pid), cSize(size), permission(permission), KObject(state, KType::KTransferMemory) {
if (pid) {
KTransferMemory::KTransferMemory(const DeviceState &state, bool host, u64 address, size_t size, const memory::Permission permission, memory::MemoryState memState) : host(host), size(size), KMemory(state, KType::KTransferMemory) {
BlockDescriptor block{
.size = size,
.permission = permission,
};
ChunkDescriptor chunk{
.size = size,
.state = memState,
.blockList = {block},
};
if (host) {
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0), -1, 0));
if (reinterpret_cast<void *>(address) == MAP_FAILED)
throw exception("An error occurred while mapping transfer memory in host");
this->address = address;
chunk.address = address;
chunk.blockList.front().address = address;
hostChunk = chunk;
} else {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
@ -13,89 +30,152 @@ namespace skyline::kernel::type {
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, pid);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while mapping shared region in child process");
cAddress = fregs.x0;
} else {
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0), -1, 0));
if (reinterpret_cast<void *>(address) == MAP_FAILED)
throw exception("An error occurred while mapping transfer memory in kernel");
cAddress = address;
this->address = fregs.x0;
chunk.address = fregs.x0;
chunk.blockList.front().address = fregs.x0;
state.os->memory.InsertChunk(chunk);
}
}
u64 KTransferMemory::Transfer(pid_t process, u64 address, u64 size) {
if (process) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64 >(permission.Get());
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, process);
if (fregs.x0 < 0)
throw exception("An error occurred while mapping transfer memory in child process");
address = fregs.x0;
} else {
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0), -1, 0));
if (reinterpret_cast<void *>(address) == MAP_FAILED)
throw exception("An error occurred while mapping transfer memory in kernel");
u64 KTransferMemory::Transfer(bool mHost, u64 nAddress, u64 nSize) {
nSize = nSize ? nSize : size;
ChunkDescriptor chunk = host ? hostChunk : *state.os->memory.GetChunk(address);
chunk.address = nAddress;
chunk.size = nSize;
MemoryManager::ResizeChunk(&chunk, nSize);
for (auto &block : chunk.blockList) {
block.address = nAddress + (block.address - address);
if ((mHost && !host) || (!mHost && !host)) {
Registers fregs{};
fregs.x0 = block.address;
fregs.x1 = block.size;
fregs.x2 = (block.permission.w) ? static_cast<u64>(block.permission.Get()) : (PROT_READ | PROT_WRITE);
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((nAddress) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while mapping transfer memory in child process");
nAddress = fregs.x0;
} else if ((!mHost && host) || (mHost && host)) {
nAddress = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(block.address), block.size, block.permission.Get(), MAP_ANONYMOUS | MAP_PRIVATE | ((nAddress) ? MAP_FIXED : 0), -1, 0));
if (reinterpret_cast<void *>(nAddress) == MAP_FAILED)
throw exception("An error occurred while mapping transfer memory in host");
}
if (block.permission.r) {
if (mHost && !host)
state.process->ReadMemory(reinterpret_cast<void *>(nAddress), address, block.size);
else if (!mHost && host)
state.process->WriteMemory(reinterpret_cast<void *>(address), nAddress, block.size);
else if (!mHost && !host)
state.process->CopyMemory(address, nAddress, block.size);
else if (mHost && host)
memcpy(reinterpret_cast<void *>(nAddress), reinterpret_cast<void *>(address), block.size);
}
if (!block.permission.w) {
if (mHost) {
if (mprotect(reinterpret_cast<void *>(block.address), block.size, block.permission.Get()) == reinterpret_cast<u64>(MAP_FAILED))
throw exception("An error occurred while remapping transfer memory: {}", strerror(errno));
} else {
Registers fregs{};
fregs.x0 = block.address;
fregs.x1 = block.size;
fregs.x2 = static_cast<u64>(block.permission.Get());
fregs.x8 = __NR_mprotect;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while updating transfer memory's permissions in guest");
}
}
}
size_t copySz = std::min(size, cSize);
if (process && !owner) {
state.process->WriteMemory(reinterpret_cast<void *>(cAddress), address, copySz);
} else if (!process && owner) {
state.process->ReadMemory(reinterpret_cast<void *>(address), cAddress, copySz);
} else
throw exception("Transferring from kernel to kernel is not supported");
if (owner) {
if (mHost && !host) {
state.os->memory.DeleteChunk(address);
hostChunk = chunk;
} else if (!mHost && host)
state.os->memory.InsertChunk(chunk);
else if (mHost && host)
hostChunk = chunk;
else if (!mHost && !host) {
state.os->memory.DeleteChunk(address);
state.os->memory.InsertChunk(chunk);
}
if ((mHost && !host) || (!mHost && !host)) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, owner);
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while unmapping transfer memory in child process");
} else {
} else if ((!mHost && host) || (mHost && host)) {
if (reinterpret_cast<void *>(munmap(reinterpret_cast<void *>(address), size)) == MAP_FAILED)
throw exception("An error occurred while unmapping transfer memory in kernel");
throw exception("An error occurred while unmapping transfer memory in host: {}");
}
owner = process;
cAddress = address;
cSize = size;
host = mHost;
address = nAddress;
size = nSize;
return address;
}
memory::MemoryInfo KTransferMemory::GetInfo() {
memory::MemoryInfo info{};
info.baseAddress = cAddress;
info.size = cSize;
info.type = static_cast<u64>(memory::Type::TransferMemory);
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);
info.r = permission.r;
info.w = permission.w;
info.x = permission.x;
info.ipcRefCount = ipcRefCount;
info.deviceRefCount = deviceRefCount;
return info;
void KTransferMemory::Resize(size_t nSize) {
if (host) {
if (mremap(reinterpret_cast<void *>(address), size, nSize, 0) == MAP_FAILED)
throw exception("An error occurred while remapping transfer memory in host: {}", strerror(errno));
} else {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = nSize;
fregs.x8 = __NR_mremap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while remapping transfer memory in guest");
size = nSize;
auto chunk = state.os->memory.GetChunk(address);
MemoryManager::ResizeChunk(chunk, size);
}
}
void KTransferMemory::UpdatePermission(const u64 address, const u64 size, memory::Permission permission) {
BlockDescriptor block{
.address = address,
.size = size,
.permission = permission,
};
if (host) {
if (mprotect(reinterpret_cast<void *>(address), size, permission.Get()) == reinterpret_cast<u64>(MAP_FAILED))
throw exception("An occurred while remapping transfer memory: {}", strerror(errno));
MemoryManager::InsertBlock(&hostChunk, block);
} else {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x8 = __NR_mprotect;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
if (fregs.x0 < 0)
throw exception("An error occurred while updating transfer memory's permissions in guest");
auto chunk = state.os->memory.GetChunk(address);
MemoryManager::InsertBlock(chunk, block);
}
}
KTransferMemory::~KTransferMemory() {
if (owner) {
if (host)
munmap(reinterpret_cast<void *>(address), size);
else if (state.process) {
try {
if (state.process) {
Registers fregs{};
fregs.x0 = cAddress;
fregs.x1 = cSize;
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.process->pid);
}
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
state.os->memory.DeleteChunk(address);
} catch (const std::exception &) {
}
} else
munmap(reinterpret_cast<void *>(cAddress), cSize);
}
}
};

View File

@ -1,45 +1,70 @@
#pragma once
#include <memory.h>
#include "KObject.h"
#include "KMemory.h"
namespace skyline::kernel::type {
/**
* @brief KTransferMemory is used to hold a particular amount of transferable memory
*/
class KTransferMemory : public KObject {
class KTransferMemory : public KMemory {
private:
ChunkDescriptor hostChunk{};
public:
pid_t owner; //!< The PID of the process owning this memory
u64 cAddress; //!< The current address of the allocated memory for the kernel
size_t cSize; //!< The current size of the allocated memory
u16 ipcRefCount{}; //!< The amount of reference to this memory for IPC
u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC
memory::Permission permission; //!< The permissions of the memory
bool host; //!< If the memory is mapped on the host or the guest
u64 address; //!< The current address of the allocated memory for the kernel
size_t size; //!< The current size of the allocated memory
/**
* @param state The state of the device
* @param pid The PID of the owner thread (Use 0 for kernel)
* @param host If to map the memory on host or guest
* @param address The address to map to (If NULL an arbitrary address is picked)
* @param size The size of the allocation
* @param permission The permissions of the memory
* @param type The type of the memory
* @param memState The MemoryState of the chunk of memory
*/
KTransferMemory(const DeviceState &state, pid_t pid, u64 address, size_t size, const memory::Permission permission);
KTransferMemory(const DeviceState &state, bool host, u64 address, size_t size, const memory::Permission permission, memory::MemoryState memState = memory::MemoryStates::TransferMemory);
/**
* @brief Transfers this piece of memory to another process
* @param process The PID of the process (Use 0 for kernel)
* @param host If to transfer memory to host or guest
* @param address The address to map to (If NULL an arbitrary address is picked)
* @param size The amount of shared memory to map
* @return The address of the allocation
*/
u64 Transfer(pid_t process, u64 address, u64 size);
u64 Transfer(bool host, u64 address, u64 size = 0);
/**
* @brief Returns a MemoryInfo struct filled with attributes of this region of memory
* @return A memory::MemoryInfo struct based on attributes of the memory
* @brief Remap a chunk of memory as to change the size occupied by it
* @param size The new size of the memory
* @return The address the memory was remapped to
*/
memory::MemoryInfo GetInfo();
virtual void Resize(size_t size);
/**
* @brief Updates the permissions of a block of mapped memory
* @param address The starting address to change the permissions at
* @param size The size of the partition to change the permissions of
* @param permission The new permissions to be set for the memory
*/
virtual void UpdatePermission(const u64 address, const u64 size, memory::Permission permission);
/**
* @brief Updates the permissions of a chunk of mapped memory
* @param permission The new permissions to be set for the memory
*/
inline virtual void UpdatePermission(memory::Permission permission) {
UpdatePermission(address, size, permission);
}
/**
* @brief Checks if the specified address is within the memory object
* @param address The address to check
* @return If the address is inside the memory object
*/
inline virtual bool IsInside(u64 address) {
return (this->address <= address) && ((this->address + this->size) > address);
}
/**
* @brief The destructor of private memory, it deallocates the memory

View File

@ -22,8 +22,6 @@ namespace skyline::loader {
}
public:
u64 mainEntry{}; //!< The address of the actual entry point for the application
/**
* @param filePath The path to the ROM file
*/

View File

@ -1,4 +1,5 @@
#include <vector>
#include <kernel/memory.h>
#include "nro.h"
namespace skyline::loader {
@ -6,7 +7,6 @@ namespace skyline::loader {
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
if (header.magic != constant::NroMagic)
throw exception("Invalid NRO magic! 0x{0:X}", header.magic);
mainEntry = constant::BaseAddr;
}
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
@ -18,31 +18,34 @@ namespace skyline::loader {
ReadOffset(rodata.data(), header.ro.offset, header.ro.size);
ReadOffset(data.data(), header.data.offset, header.data.size);
std::vector<u32> patch = state.nce->PatchCode(text, constant::BaseAddr, header.text.size + header.ro.size + header.data.size + header.bssSize);
std::vector<u32> patch = state.nce->PatchCode(text, constant::BaseAddress, header.text.size + header.ro.size + header.data.size + header.bssSize);
u64 textSize = text.size();
u64 rodataSize = rodata.size();
u64 dataSize = data.size();
u64 patchSize = patch.size() * sizeof(u32);
u64 padding = utils::AlignUp(textSize + rodataSize + dataSize + header.bssSize + patchSize, PAGE_SIZE) - (textSize + rodataSize + dataSize + header.bssSize + patchSize);
process->MapPrivateRegion(constant::BaseAddr, textSize, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // R-X
state.logger->Debug("Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr, textSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress, textSize, memory::Permission{true, true, true}, memory::MemoryStates::CodeStatic); // R-X
state.logger->Debug("Successfully mapped section .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress, textSize);
process->MapPrivateRegion(constant::BaseAddr + textSize, rodataSize, {true, false, false}, memory::Type::CodeReadOnly, memory::Region::RoData); // R--
state.logger->Debug("Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize, rodataSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize, rodataSize, memory::Permission{true, false, false}, memory::MemoryStates::CodeReadOnly); // R--
state.logger->Debug("Successfully mapped section .ro @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize, rodataSize);
process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize, dataSize, {true, true, false}, memory::Type::CodeStatic, memory::Region::Data); // RW-
state.logger->Debug("Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize, dataSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize, dataSize, memory::Permission{true, true, false}, memory::MemoryStates::CodeStatic); // RW-
state.logger->Debug("Successfully mapped section .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize, dataSize);
process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize + dataSize, header.bssSize, {true, true, true}, memory::Type::CodeMutable, memory::Region::Bss); // RWX
state.logger->Debug("Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize + dataSize, header.bssSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize + dataSize, header.bssSize, memory::Permission{true, true, true}, memory::MemoryStates::CodeMutable); // RWX
state.logger->Debug("Successfully mapped section .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize, header.bssSize);
process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize + dataSize + header.bssSize, patchSize, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // RWX
state.logger->Debug("Successfully mapped region .patch @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize + dataSize + header.bssSize, patchSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize + padding, memory::Permission{true, true, true}, memory::MemoryStates::CodeStatic); // RWX
state.logger->Debug("Successfully mapped section .patch @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize);
process->WriteMemory(text.data(), constant::BaseAddr, textSize);
process->WriteMemory(rodata.data(), constant::BaseAddr + textSize, rodataSize);
process->WriteMemory(data.data(), constant::BaseAddr + textSize + rodataSize, dataSize);
process->WriteMemory(patch.data(), constant::BaseAddr + textSize + rodataSize + dataSize + header.bssSize, patchSize);
process->WriteMemory(text.data(), constant::BaseAddress, textSize);
process->WriteMemory(rodata.data(), constant::BaseAddress + textSize, rodataSize);
process->WriteMemory(data.data(), constant::BaseAddress + textSize + rodataSize, dataSize);
process->WriteMemory(patch.data(), constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize);
state.os->memory.InitializeRegions(constant::BaseAddress, textSize + rodataSize + dataSize + header.bssSize + patchSize + padding, memory::AddressSpaceType::AddressSpace39Bit);
}
}

View File

@ -1,138 +0,0 @@
#pragma once
#include "common.h"
namespace skyline::memory {
/**
* @brief The Permission struct holds the permission of a particular chunk of memory
*/
struct Permission {
/**
* @brief This constructor initializes all permissions to false
*/
Permission() {
r = 0;
w = 0;
x = 0;
};
/**
* @param read If memory has read permission
* @param write If memory has write permission
* @param execute If memory has execute permission
*/
Permission(bool read, bool write, bool execute) {
r = read;
w = write;
x = execute;
};
/**
* @brief Equality operator between two Permission objects
*/
inline bool operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); };
/**
* @brief Inequality operator between two Permission objects
*/
inline bool operator!=(const Permission &rhs) const { return !operator==(rhs); };
/**
* @return The value of the permission struct in mmap(2) format
*/
int Get() const {
int perm = 0;
if (r)
perm |= PROT_READ;
if (w)
perm |= PROT_WRITE;
if (x)
perm |= PROT_EXEC;
return perm;
};
bool r;
bool w;
bool x;
};
/**
* @brief This holds certain attributes of a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryAttribute
*/
union MemoryAttribute {
struct {
bool isBorrowed : 1;
bool isIpcLocked : 1;
bool isDeviceShared : 1;
bool isUncached : 1;
};
u32 value;
};
static_assert(sizeof(MemoryAttribute) == sizeof(u32));
/**
* @brief This describes the properties of a region of the allocated memory
*/
struct RegionInfo {
u64 address; //!< The starting address of the chunk of memory
u64 size; //!< The size of the chunk of memory
bool isUncached; //!< If the following region is uncached
RegionInfo(u64 address, u64 size, bool isUncached) : address(address), size(size), isUncached(isUncached) {}
};
/**
* @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
*/
struct MemoryInfo {
u64 baseAddress;
u64 size;
u32 type;
MemoryAttribute memoryAttribute;
union {
u32 _pad0_;
struct {
bool r : 1, w : 1, x : 1;
};
};
u32 ipcRefCount;
u32 deviceRefCount;
u32 : 32;
};
static_assert(sizeof(MemoryInfo) == 0x28);
/**
* @brief These are specific markers for the type of a memory region
*/
enum class Type : u32 {
Unmapped = 0x00000000,
Io = 0x00002001,
Normal = 0x00042002,
CodeStatic = 0x00DC7E03,
CodeMutable = 0x03FEBD04,
Heap = 0x037EBD05,
SharedMemory = 0x00402006,
Alias = 0x00482907,
ModuleCodeStatic = 0x00DD7E08,
ModuleCodeMutable = 0x03FFBD09,
Ipc = 0x005C3C0A,
Stack = 0x005C3C0B,
ThreadLocal = 0x0040200C,
TransferMemoryIsolated = 0x015C3C0D,
TransferMemory = 0x005C380E,
ProcessMemory = 0x0040380F,
Reserved = 0x00000010,
NonSecureIpc = 0x005C3811,
NonDeviceIpc = 0x004C2812,
KernelStack = 0x00002013,
CodeReadOnly = 0x00402214,
CodeWritable = 0x00402015
};
/**
* @brief Memory Regions that are mapped by the kernel
*/
enum class Region {
Heap, Text, RoData, Data, Bss
};
}

View File

@ -5,6 +5,7 @@
#include "nce/guest.h"
#include "nce/instr.h"
#include "kernel/svc.h"
#include "nce.h"
extern bool Halt;
extern skyline::GroupMutex jniMtx;
@ -12,8 +13,8 @@ extern skyline::GroupMutex jniMtx;
namespace skyline {
void NCE::KernelThread(pid_t thread) {
try {
state.thread = state.process->threadMap.at(thread);
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->guest.address);
state.thread = state.process->threads.at(thread);
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->kernel.address);
while (true) {
std::lock_guard jniGd(jniMtx);
if (Halt)
@ -91,11 +92,9 @@ namespace skyline {
ExecuteFunctionCtx(call, funcRegs, reinterpret_cast<ThreadContext *>(thread->ctxMemory->kernel.address));
}
void NCE::ExecuteFunction(ThreadCall call, Registers &funcRegs, pid_t pid) {
if (state.process->status != kernel::type::KProcess::Status::Exiting)
ExecuteFunctionCtx(call, funcRegs, reinterpret_cast<ThreadContext *>(state.process->threadMap.at(pid)->ctxMemory->kernel.address));
else
throw std::out_of_range("The KProcess object is missing");
void NCE::ExecuteFunction(ThreadCall call, Registers &funcRegs) {
auto thread = state.thread ? state.thread : state.process->threads.at(state.process->pid);
ExecuteFunctionCtx(call, funcRegs, reinterpret_cast<ThreadContext *>(thread->ctxMemory->kernel.address));
}
void NCE::WaitThreadInit(std::shared_ptr<kernel::type::KThread> &thread) __attribute__ ((optnone)) {
@ -339,7 +338,6 @@ namespace skyline {
offset -= sizeof(u32);
patchOffset -= sizeof(u32);
}
patch.resize(patch.size() + PAGE_SIZE - 1 & ~(PAGE_SIZE - 1), 0x0);
return patch;
}
}

View File

@ -37,7 +37,7 @@ namespace skyline {
void Execute();
/**
* @brief Execute any arbitrary function on a particular child process
* @brief Execute any arbitrary function on a specific child thread
* @param call The specific call to execute
* @param funcRegs A set of registers to run the function
* @param thread The thread to execute the function on
@ -45,12 +45,11 @@ namespace skyline {
void ExecuteFunction(ThreadCall call, Registers &funcRegs, std::shared_ptr<kernel::type::KThread> &thread);
/**
* @brief Execute any arbitrary function on a particular child process
* @brief Execute any arbitrary function on the child process
* @param call The specific call to execute
* @param funcRegs A set of registers to run the function
* @param pid The PID of the process
*/
void ExecuteFunction(ThreadCall call, Registers &funcRegs, pid_t pid);
void ExecuteFunction(ThreadCall call, Registers &funcRegs);
/**
* @brief Waits till a thread is ready to execute commands

View File

@ -172,6 +172,13 @@ namespace skyline::guest {
"LDR LR, [SP], #16");
saveCtxTls();
loadCtxStack();
} else if (ctx->commandId == static_cast<u32>(ThreadCall::Memcopy)) {
auto src = reinterpret_cast<u8*>(ctx->registers.x0);
auto dest = reinterpret_cast<u8*>(ctx->registers.x1);
auto size = ctx->registers.x2;
auto end = src + size;
while (src < end)
*(src++) = *(dest++);
}
}
}
@ -214,6 +221,13 @@ namespace skyline::guest {
saveCtxTls();
loadCtxStack();
}
} else if (ctx->commandId == static_cast<u32>(ThreadCall::Memcopy)) {
auto src = reinterpret_cast<u8*>(ctx->registers.x0);
auto dest = reinterpret_cast<u8*>(ctx->registers.x1);
auto size = ctx->registers.x2;
auto end = src + size;
while (src < end)
*(src++) = *(dest++);
}
}
struct sigaction sigact{

View File

@ -5,9 +5,9 @@ namespace skyline {
constexpr size_t saveCtxSize = 20 * sizeof(u32);
constexpr size_t loadCtxSize = 20 * sizeof(u32);
#ifdef NDEBUG
constexpr size_t svcHandlerSize = 150 * sizeof(u32);
constexpr size_t svcHandlerSize = 175 * sizeof(u32);
#else
constexpr size_t svcHandlerSize = 250 * sizeof(u32);
constexpr size_t svcHandlerSize = 275 * sizeof(u32);
#endif
void entry(u64 address);

View File

@ -121,12 +121,12 @@ namespace skyline {
* @brief This enumeration is used to convey the state of a thread to the kernel
*/
enum class ThreadState : u32 {
NotReady = 0, //!< The thread hasn't yet entered the entry handler
Running = 1, //!< The thread is currently executing code
NotReady = 0, //!< The thread hasn't yet entered the entry handler
Running = 1, //!< The thread is currently executing code
WaitKernel = 2, //!< The thread is currently waiting on the kernel
WaitRun = 3, //!< The thread should be ready to run
WaitInit = 4, //!< The thread is waiting to be initialized
WaitFunc = 5, //!< The kernel is waiting for the thread to run a function
WaitRun = 3, //!< The thread should be ready to run
WaitInit = 4, //!< The thread is waiting to be initialized
WaitFunc = 5, //!< The kernel is waiting for the thread to run a function
GuestCrash = 6, //!< This is a notification to the kernel that the guest has crashed
};
@ -135,6 +135,7 @@ namespace skyline {
*/
enum class ThreadCall : u32 {
Syscall = 0x100, //!< A linux syscall needs to be called from the guest
Memcopy = 0x101, //!< To copy memory from one location to another
};
/**

View File

@ -3,7 +3,7 @@
#include "nce/guest.h"
namespace skyline::kernel {
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, jvmManager, settings, logger), serviceManager(state) {}
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state) {}
void OS::Execute(const int romFd, const TitleFormat romType) {
std::shared_ptr<loader::Loader> loader;
@ -11,9 +11,10 @@ namespace skyline::kernel {
loader = std::make_shared<loader::NroLoader>(romFd);
} else
throw exception("Unsupported ROM extension.");
auto process = CreateProcess(loader->mainEntry, 0, constant::DefStackSize);
auto process = CreateProcess(constant::BaseAddress, 0, constant::DefStackSize);
loader->LoadProcessData(process, state);
process->threadMap.at(process->pid)->Start(); // The kernel itself is responsible for starting the main thread
process->InitializeMemory();
process->threads.at(process->pid)->Start(); // The kernel itself is responsible for starting the main thread
state.nce->Execute();
}
@ -25,7 +26,7 @@ namespace skyline::kernel {
munmap(stack, stackSize);
throw exception("Failed to create guard pages");
}
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission(true, true, false), memory::Type::Reserved);
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission(true, true, false), memory::MemoryStates::Reserved);
tlsMem->guest = tlsMem->kernel;
pid_t pid = clone(reinterpret_cast<int (*)(void *)>(&guest::entry), stack + stackSize, CLONE_FILES | CLONE_FS | CLONE_SETTLS | SIGCHLD, reinterpret_cast<void *>(entry), nullptr, reinterpret_cast<void *>(tlsMem->guest.address));
if (pid == -1)
@ -39,11 +40,11 @@ namespace skyline::kernel {
void OS::KillThread(pid_t pid) {
if (process->pid == pid) {
state.logger->Debug("Killing process with PID: {}", pid);
for (auto &thread: process->threadMap)
for (auto &thread: process->threads)
thread.second->Kill();
} else {
state.logger->Debug("Killing thread with TID: {}", pid);
process->threadMap.at(pid)->Kill();
process->threads.at(pid)->Kill();
}
}
}

View File

@ -20,6 +20,7 @@ namespace skyline::kernel {
public:
std::shared_ptr<type::KProcess> process; //!< The KProcess object for the emulator, representing the guest process
service::ServiceManager serviceManager; //!< This manages all of the service functions
MemoryManager memory; //!< The MemoryManager object for this process
/**
* @param logger An instance of the Logger class

View File

@ -7,7 +7,7 @@ namespace skyline::service::hid {
}) {}
void IAppletResource::GetSharedMemoryHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
hidSharedMemory = std::make_shared<kernel::type::KSharedMemory>(state, NULL, constant::hidSharedMemSize, memory::Permission(true, false, false), memory::Type::SharedMemory);
hidSharedMemory = std::make_shared<kernel::type::KSharedMemory>(state, NULL, constant::hidSharedMemSize, memory::Permission{true, false, false});
auto handle = state.process->InsertItem<type::KSharedMemory>(hidSharedMemory);
state.logger->Debug("HID Shared Memory Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle);