skyline/app/src/main/cpp/skyline/kernel/types/KProcess.cpp

375 lines
13 KiB
C++

// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <asm/unistd.h>
#include <nce/guest.h>
#include <nce.h>
#include <os.h>
#include "KProcess.h"
namespace skyline::kernel::type {
KProcess::TlsPage::TlsPage(u64 address) : address(address) {}
u64 KProcess::TlsPage::ReserveSlot() {
if (Full())
throw exception("Trying to get TLS slot from full page");
slot[index] = true;
return Get(index++); // ++ on right will cause increment after evaluation of expression
}
u64 KProcess::TlsPage::Get(u8 slotNo) {
if (slotNo >= constant::TlsSlots)
throw exception("TLS slot is out of range");
return address + (constant::TlsSlotSize * slotNo);
}
bool KProcess::TlsPage::Full() {
return slot[constant::TlsSlots - 1];
}
u64 KProcess::GetTlsSlot() {
for (auto &tlsPage: tlsPages)
if (!tlsPage->Full())
return tlsPage->ReserveSlot();
u64 address;
if (tlsPages.empty()) {
auto region = state.os->memory.GetRegion(memory::Regions::TlsIo);
address = region.size ? region.address : 0;
} else {
address = (*(tlsPages.end() - 1))->address + PAGE_SIZE;
}
auto tlsMem = NewHandle<KPrivateMemory>(address, PAGE_SIZE, memory::Permission(true, true, false), memory::states::ThreadLocal).item;
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
auto &tlsPage = tlsPages.back();
if (tlsPages.empty())
tlsPage->ReserveSlot(); // User-mode exception handling
return tlsPage->ReserveSlot();
}
void KProcess::InitializeMemory() {
constexpr size_t DefHeapSize = 0x200000; // The default amount of heap
heap = NewHandle<KPrivateMemory>(state.os->memory.GetRegion(memory::Regions::Heap).address, DefHeapSize, memory::Permission{true, true, false}, memory::states::Heap).item;
threads[pid]->tls = GetTlsSlot();
}
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, std::shared_ptr<type::KSharedMemory> &stack, std::shared_ptr<type::KSharedMemory> &tlsMemory) : pid(pid), stack(stack), KSyncObject(state, KType::KProcess) {
constexpr auto DefaultPriority = 44; // The default priority of a process
auto thread = NewHandle<KThread>(pid, entryPoint, 0x0, stack->guest.address + stack->guest.size, 0, DefaultPriority, this, tlsMemory).item;
threads[pid] = thread;
state.nce->WaitThreadInit(thread);
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));
}
KProcess::~KProcess() {
close(memFd);
status = Status::Exiting;
}
std::shared_ptr<KThread> KProcess::CreateThread(u64 entryPoint, u64 entryArg, u64 stackTop, u8 priority) {
auto size = (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, size, memory::Permission{true, true, false}, memory::states::Reserved);
Registers fregs{
.x0 = CLONE_THREAD | CLONE_SIGHAND | CLONE_PTRACE | CLONE_FS | CLONE_VM | CLONE_FILES | CLONE_IO,
.x1 = stackTop,
.x3 = tlsMem->Map(0, size, memory::Permission{true, true, false}),
.x8 = __NR_clone,
.x5 = reinterpret_cast<u64>(&guest::GuestEntry),
.x6 = entryPoint,
};
state.nce->ExecuteFunction(ThreadCall::Clone, fregs);
if (static_cast<int>(fregs.x0) < 0)
throw exception("Cannot create thread: Address: 0x{:X}, Stack Top: 0x{:X}", entryPoint, stackTop);
auto pid = static_cast<pid_t>(fregs.x0);
auto process = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this, tlsMem).item;
threads[pid] = process;
return process;
}
u64 KProcess::GetHostAddress(u64 address) const {
auto chunk = state.os->memory.GetChunk(address);
return (chunk && chunk->host) ? chunk->host + (address - chunk->address) : 0;
}
void KProcess::ReadMemory(void *destination, const u64 offset, const size_t size, const bool forceGuest) const {
if (!forceGuest) {
auto source = GetHostAddress(offset);
if (source) {
std::memcpy(destination, reinterpret_cast<void *>(source), size);
return;
}
}
struct iovec local{
.iov_base = destination,
.iov_len = size,
};
struct iovec remote{
.iov_base = reinterpret_cast<void *>(offset),
.iov_len = size,
};
if (process_vm_readv(pid, &local, 1, &remote, 1, 0) < 0)
pread64(memFd, destination, size, offset);
}
void KProcess::WriteMemory(const void *source, const u64 offset, const size_t size, const bool forceGuest) const {
if (!forceGuest) {
auto destination = GetHostAddress(offset);
if (destination) {
std::memcpy(reinterpret_cast<void *>(destination), source, size);
return;
}
}
struct iovec local{
.iov_base = const_cast<void *>(source),
.iov_len = size,
};
struct iovec remote{
.iov_base = reinterpret_cast<void *>(offset),
.iov_len = size,
};
if (process_vm_writev(pid, &local, 1, &remote, 1, 0) < 0)
pwrite64(memFd, source, size, offset);
}
void KProcess::CopyMemory(u64 source, u64 destination, size_t size) const {
auto sourceHost = GetHostAddress(source);
auto destinationHost = GetHostAddress(destination);
if (sourceHost && destinationHost) {
std::memcpy(reinterpret_cast<void *>(destinationHost), reinterpret_cast<const void *>(sourceHost), size);
} else {
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{
.x0 = source,
.x1 = destination,
.x2 = size,
};
state.nce->ExecuteFunction(ThreadCall::Memcopy, fregs);
}
}
}
std::optional<KProcess::HandleOut<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 std::make_optional<KProcess::HandleOut<KMemory>>({mem, handle});
}
default:
break;
}
}
return std::nullopt;
}
bool KProcess::MutexLock(u64 address, KHandle owner) {
std::unique_lock lock(mutexLock);
auto mtx = GetPointer<u32>(address);
auto &mtxWaiters = mutexes[address];
if (mtxWaiters.empty()) {
u32 mtxExpected = 0;
if (__atomic_compare_exchange_n(mtx, &mtxExpected, (constant::MtxOwnerMask & state.thread->handle), false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
return true;
}
if (__atomic_load_n(mtx, __ATOMIC_SEQ_CST) != (owner | ~constant::MtxOwnerMask))
return false;
std::shared_ptr<WaitStatus> status;
for (auto it = mtxWaiters.begin();; ++it) {
if (it != mtxWaiters.end() && (*it)->priority >= state.thread->priority)
continue;
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle);
mtxWaiters.insert(it, status);
break;
}
lock.unlock();
while (!status->flag);
lock.lock();
status->flag = false;
for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it) {
if ((*it)->handle == state.thread->handle) {
mtxWaiters.erase(it);
break;
}
}
return true;
}
bool KProcess::MutexUnlock(u64 address) {
std::unique_lock lock(mutexLock);
auto mtx = GetPointer<u32>(address);
auto &mtxWaiters = mutexes[address];
u32 mtxDesired{};
if (!mtxWaiters.empty())
mtxDesired = (*mtxWaiters.begin())->handle | ((mtxWaiters.size() > 1) ? ~constant::MtxOwnerMask : 0);
u32 mtxExpected = (constant::MtxOwnerMask & state.thread->handle) | ~constant::MtxOwnerMask;
if (!__atomic_compare_exchange_n(mtx, &mtxExpected, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
mtxExpected &= constant::MtxOwnerMask;
if (!__atomic_compare_exchange_n(mtx, &mtxExpected, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
return false;
}
if (mtxDesired) {
auto status = (*mtxWaiters.begin());
status->flag = true;
lock.unlock();
while (status->flag);
lock.lock();
}
return true;
}
bool KProcess::ConditionalVariableWait(u64 conditionalAddress, u64 mutexAddress, u64 timeout) {
std::unique_lock lock(conditionalLock);
auto &condWaiters = conditionals[conditionalAddress];
std::shared_ptr<WaitStatus> status;
for (auto it = condWaiters.begin();; ++it) {
if (it != condWaiters.end() && (*it)->priority >= state.thread->priority)
continue;
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle, mutexAddress);
condWaiters.insert(it, status);
break;
}
lock.unlock();
bool timedOut{};
auto start = util::GetTimeNs();
while (!status->flag)
if ((util::GetTimeNs() - start) >= timeout)
timedOut = true;
lock.lock();
if (!status->flag)
timedOut = false;
else
status->flag = false;
for (auto it = condWaiters.begin(); it != condWaiters.end(); ++it) {
if ((*it)->handle == state.thread->handle) {
condWaiters.erase(it);
break;
}
}
lock.unlock();
return !timedOut;
}
void KProcess::ConditionalVariableSignal(u64 address, u64 amount) {
std::unique_lock condLock(conditionalLock);
auto &condWaiters = conditionals[address];
u64 count{};
auto iter = condWaiters.begin();
while (iter != condWaiters.end() && count < amount) {
auto &thread = *iter;
auto mtx = GetPointer<u32>(thread->mutexAddress);
u32 mtxValue = __atomic_load_n(mtx, __ATOMIC_SEQ_CST);
while (true) {
u32 mtxDesired{};
if (!mtxValue)
mtxDesired = (constant::MtxOwnerMask & thread->handle);
else if ((mtxValue & constant::MtxOwnerMask) == state.thread->handle)
mtxDesired = mtxValue | (constant::MtxOwnerMask & thread->handle);
else if (mtxValue & ~constant::MtxOwnerMask)
mtxDesired = mtxValue | ~constant::MtxOwnerMask;
else
break;
if (__atomic_compare_exchange_n(mtx, &mtxValue, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
break;
}
if (mtxValue && ((mtxValue & constant::MtxOwnerMask) != state.thread->handle)) {
std::unique_lock mtxLock(mutexLock);
auto &mtxWaiters = mutexes[thread->mutexAddress];
std::shared_ptr<WaitStatus> status;
for (auto it = mtxWaiters.begin();; ++it) {
if (it != mtxWaiters.end() && (*it)->priority >= thread->priority)
continue;
status = std::make_shared<WaitStatus>(thread->priority, thread->handle);
mtxWaiters.insert(it, status);
break;
}
mtxLock.unlock();
while (!status->flag);
mtxLock.lock();
status->flag = false;
for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it) {
if ((*it)->handle == thread->handle) {
mtxWaiters.erase(it);
break;
}
}
mtxLock.unlock();
}
thread->flag = true;
iter++;
count++;
condLock.unlock();
while (thread->flag);
condLock.lock();
}
}
}