NCE3: In-Process Guest Execution

This commit is contained in:
◱ PixelyIon 2020-10-10 21:23:14 +05:30 committed by ◱ PixelyIon
parent 90127740f0
commit 1db76dee1e
17 changed files with 705 additions and 746 deletions

View File

@ -34,7 +34,6 @@ add_library(skyline SHARED
${source_DIR}/loader_jni.cpp
${source_DIR}/skyline/common.cpp
${source_DIR}/skyline/nce/guest.S
${source_DIR}/skyline/nce/guest.cpp
${source_DIR}/skyline/nce.cpp
${source_DIR}/skyline/jvm.cpp
${source_DIR}/skyline/audio.cpp

View File

@ -157,12 +157,12 @@ namespace skyline {
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &process, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)
: os(os), jvm(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)), process(process) {
// We assign these later as they use the state in their constructor and we don't want null pointers
nce = std::make_shared<NCE>(*this);
nce = std::make_shared<nce::NCE>(*this);
gpu = std::make_shared<gpu::GPU>(*this);
audio = std::make_shared<audio::Audio>(*this);
input = std::make_shared<input::Input>(*this);
}
thread_local std::shared_ptr<kernel::type::KThread> DeviceState::thread = nullptr;
thread_local ThreadContext *DeviceState::ctx = nullptr;
thread_local nce::ThreadContext *DeviceState::ctx = nullptr;
}

View File

@ -23,11 +23,23 @@
#include <frozen/unordered_map.h>
#include <frozen/string.h>
#include <jni.h>
#include "nce/guest_common.h"
#define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage)
namespace skyline {
namespace frz = frozen;
using u128 = __uint128_t; //!< Unsigned 128-bit integer
using u64 = __uint64_t; //!< Unsigned 64-bit integer
using u32 = __uint32_t; //!< Unsigned 32-bit integer
using u16 = __uint16_t; //!< Unsigned 16-bit integer
using u8 = __uint8_t; //!< Unsigned 8-bit integer
using i128 = __int128_t; //!< Signed 128-bit integer
using i64 = __int64_t; //!< Signed 64-bit integer
using i32 = __int32_t; //!< Signed 32-bit integer
using i16 = __int16_t; //!< Signed 16-bit integer
using i8 = __int8_t; //!< Signed 8-bit integer
using KHandle = u32; //!< The type of a kernel handle
namespace frz = frozen;
/**
* @brief The result of an operation in HOS
@ -105,9 +117,8 @@ namespace skyline {
* @return The current time in nanoseconds
*/
inline u64 GetTimeNs() {
static u64 frequency{};
if (!frequency)
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
u64 frequency;
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
u64 ticks;
asm("MRS %0, CNTVCT_EL0" : "=r"(ticks));
return ((ticks / frequency) * constant::NsInSecond) + (((ticks % frequency) * constant::NsInSecond + (frequency / 2)) / frequency);
@ -529,6 +540,10 @@ namespace skyline {
void List(const std::shared_ptr<Logger> &logger);
};
namespace nce {
class NCE;
struct ThreadContext;
}
class JvmManager;
namespace gpu {
class GPU;
@ -559,8 +574,8 @@ namespace skyline {
kernel::OS *os;
std::shared_ptr<kernel::type::KProcess> &process;
thread_local static std::shared_ptr<kernel::type::KThread> thread; //!< The KThread of the thread which accesses this object
thread_local static ThreadContext *ctx; //!< The context of the guest thread for the corresponding host thread
std::shared_ptr<NCE> nce;
thread_local static nce::ThreadContext *ctx; //!< The context of the guest thread for the corresponding host thread
std::shared_ptr<nce::NCE> nce;
std::shared_ptr<gpu::GPU> gpu;
std::shared_ptr<audio::Audio> audio;
std::shared_ptr<input::Input> input;

View File

@ -127,7 +127,8 @@ namespace skyline::kernel {
size_t MemoryManager::GetProgramSize() {
size_t size{};
for (const auto &chunk : chunks)
size += chunk.size;
if (chunk.state != memory::states::Unmapped)
size += chunk.size;
return size;
}
}

View File

@ -108,6 +108,14 @@ namespace skyline {
constexpr MemoryState() : value(0) {}
constexpr bool operator==(const MemoryState& other) const {
return value == other.value;
}
constexpr bool operator!=(const MemoryState& other) const {
return value != other.value;
}
struct {
MemoryType type;
bool permissionChangeAllowed : 1; //!< If the application can use svcSetMemoryPermission on this block

View File

@ -7,12 +7,12 @@
#include "svc.h"
namespace skyline::kernel::svc {
void SetHeapSize(DeviceState &state) {
auto size{state.ctx->registers.w1};
void SetHeapSize(const DeviceState &state) {
auto size{state.ctx->gpr.w1};
if (!util::IsAligned(size, 0x200000)) {
state.ctx->registers.w0 = result::InvalidSize;
state.ctx->registers.x1 = 0;
state.ctx->gpr.w0 = result::InvalidSize;
state.ctx->gpr.x1 = 0;
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
return;
@ -21,46 +21,46 @@ namespace skyline::kernel::svc {
auto &heap{state.process->heap};
heap->Resize(size);
state.ctx->registers.w0 = Result{};
state.ctx->registers.x1 = reinterpret_cast<u64>(heap->ptr);
state.ctx->gpr.w0 = Result{};
state.ctx->gpr.x1 = reinterpret_cast<u64>(heap->ptr);
state.logger->Debug("svcSetHeapSize: Allocated at 0x{:X} for 0x{:X} bytes", heap->ptr, heap->size);
state.logger->Debug("svcSetHeapSize: Allocated at {} for 0x{:X} bytes", heap->ptr, heap->size);
}
void SetMemoryAttribute(DeviceState &state) {
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x0)};
void SetMemoryAttribute(const DeviceState &state) {
auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x0)};
if (!util::PageAligned(pointer)) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcSetMemoryAttribute: 'pointer' not page aligned: 0x{:X}", pointer);
return;
}
auto size{state.ctx->registers.x1};
auto size{state.ctx->gpr.x1};
if (!util::PageAligned(size)) {
state.ctx->registers.w0 = result::InvalidSize;
state.ctx->gpr.w0 = result::InvalidSize;
state.logger->Warn("svcSetMemoryAttribute: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
memory::MemoryAttribute mask{.value = state.ctx->registers.w2};
memory::MemoryAttribute value{.value = state.ctx->registers.w3};
memory::MemoryAttribute mask{.value = state.ctx->gpr.w2};
memory::MemoryAttribute value{.value = state.ctx->gpr.w3};
auto maskedValue{mask.value | value.value};
if (maskedValue != mask.value || !mask.isUncached || mask.isDeviceShared || mask.isBorrowed || mask.isIpcLocked) {
state.ctx->registers.w0 = result::InvalidCombination;
state.ctx->gpr.w0 = result::InvalidCombination;
state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask.value, value.value);
return;
}
auto chunk{state.process->memory.Get(pointer)};
if (!chunk) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", pointer);
return;
}
if (!chunk->state.attributeChangeAllowed) {
state.ctx->registers.w0 = result::InvalidState;
state.ctx->gpr.w0 = result::InvalidState;
state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", pointer);
return;
}
@ -72,41 +72,41 @@ namespace skyline::kernel::svc {
state.process->memory.InsertChunk(newChunk);
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} for 0x{:X} bytes", bool(value.isUncached), pointer, size);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void MapMemory(DeviceState &state) {
auto destination{reinterpret_cast<u8*>(state.ctx->registers.x0)};
auto source{reinterpret_cast<u8*>(state.ctx->registers.x1)};
auto size{state.ctx->registers.x2};
void MapMemory(const DeviceState &state) {
auto destination{reinterpret_cast<u8*>(state.ctx->gpr.x0)};
auto source{reinterpret_cast<u8*>(state.ctx->gpr.x1)};
auto size{state.ctx->gpr.x2};
if (!util::PageAligned(destination) || !util::PageAligned(source)) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if (!util::PageAligned(size)) {
state.ctx->registers.w0 = result::InvalidSize;
state.ctx->gpr.w0 = result::InvalidSize;
state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto stack{state.process->memory.stack};
if (!stack.IsInside(destination)) {
state.ctx->registers.w0 = result::InvalidMemoryRegion;
state.ctx->gpr.w0 = result::InvalidMemoryRegion;
state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
auto chunk{state.process->memory.Get(source)};
if (!chunk) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if (!chunk->state.mapAllowed) {
state.ctx->registers.w0 = result::InvalidState;
state.ctx->gpr.w0 = result::InvalidState;
state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X}, Size: 0x{:X}, MemoryState: 0x{:X}", source, destination, size, chunk->state.value);
return;
}
@ -120,29 +120,29 @@ namespace skyline::kernel::svc {
object->item->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, fmt::ptr(source + size), destination, fmt::ptr(destination + size), size);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void UnmapMemory(DeviceState &state) {
auto source{reinterpret_cast<u8*>(state.ctx->registers.x0)};
auto destination{reinterpret_cast<u8*>(state.ctx->registers.x1)};
auto size{state.ctx->registers.x2};
void UnmapMemory(const DeviceState &state) {
auto source{reinterpret_cast<u8*>(state.ctx->gpr.x0)};
auto destination{reinterpret_cast<u8*>(state.ctx->gpr.x1)};
auto size{state.ctx->gpr.x2};
if (!util::PageAligned(destination) || !util::PageAligned(source)) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if (!util::PageAligned(size)) {
state.ctx->registers.w0 = result::InvalidSize;
state.ctx->gpr.w0 = result::InvalidSize;
state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto stack{state.process->memory.stack};
if (!stack.IsInside(source)) {
state.ctx->registers.w0 = result::InvalidMemoryRegion;
state.ctx->gpr.w0 = result::InvalidMemoryRegion;
state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
@ -150,13 +150,13 @@ namespace skyline::kernel::svc {
auto sourceChunk{state.process->memory.Get(source)};
auto destChunk{state.process->memory.Get(destination)};
if (!sourceChunk || !destChunk) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if (!destChunk->state.mapAllowed) {
state.ctx->registers.w0 = result::InvalidState;
state.ctx->gpr.w0 = result::InvalidState;
state.logger->Warn("svcUnmapMemory: Destination doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, destChunk->state.value);
return;
}
@ -176,13 +176,13 @@ namespace skyline::kernel::svc {
state.process->CloseHandle(sourceObject->handle);
state.logger->Debug("svcUnmapMemory: Unmapped 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 = Result{};
state.ctx->gpr.w0 = Result{};
}
void QueryMemory(DeviceState &state) {
void QueryMemory(const DeviceState &state) {
memory::MemoryInfo memInfo{};
auto pointer{reinterpret_cast<u8*>(state.ctx->registers.x2)};
auto pointer{reinterpret_cast<u8*>(state.ctx->gpr.x2)};
auto chunk{state.process->memory.Get(pointer)};
if (chunk) {
@ -209,24 +209,24 @@ namespace skyline::kernel::svc {
state.logger->Debug("svcQueryMemory: Trying to query memory outside of the application's address space: 0x{:X}", pointer);
}
*reinterpret_cast<memory::MemoryInfo*>(state.ctx->registers.x0) = memInfo;
*reinterpret_cast<memory::MemoryInfo*>(state.ctx->gpr.x0) = memInfo;
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void ExitProcess(DeviceState &state) {
void ExitProcess(const DeviceState &state) {
state.logger->Debug("svcExitProcess: Exiting process");
//state.os->KillThread(state.process->pid);
}
void CreateThread(DeviceState &state) {
auto entry{reinterpret_cast<void*>(state.ctx->registers.x1)};
auto entryArgument{state.ctx->registers.x2};
auto stackTop{reinterpret_cast<u8*>(state.ctx->registers.x3)};
auto priority{static_cast<i8>(state.ctx->registers.w4)};
void CreateThread(const DeviceState &state) {
auto entry{reinterpret_cast<void*>(state.ctx->gpr.x1)};
auto entryArgument{state.ctx->gpr.x2};
auto stackTop{reinterpret_cast<u8*>(state.ctx->gpr.x3)};
auto priority{static_cast<i8>(state.ctx->gpr.w4)};
if (!constant::HosPriority.Valid(priority)) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
return;
}
@ -238,30 +238,30 @@ namespace skyline::kernel::svc {
auto thread{state.process->CreateThread(entry, entryArgument, priority, static_pointer_cast<type::KPrivateMemory>(stack->item))};
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, ID: {})", thread->handle, entry, entryArgument, stackTop, priority, thread->id);
state.ctx->registers.w1 = thread->handle;
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w1 = thread->handle;
state.ctx->gpr.w0 = Result{};
}
void StartThread(DeviceState &state) {
auto handle{state.ctx->registers.w0};
void StartThread(const DeviceState &state) {
auto handle{state.ctx->gpr.w0};
try {
auto thread{state.process->GetHandle<type::KThread>(handle)};
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->id);
thread->Start();
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void ExitThread(DeviceState &state) {
void ExitThread(const DeviceState &state) {
state.logger->Debug("svcExitThread: Exiting current thread: {}", state.thread->id);
state.os->KillThread(state.thread->id);
}
void SleepThread(DeviceState &state) {
auto in{state.ctx->registers.x0};
void SleepThread(const DeviceState &state) {
auto in{state.ctx->gpr.x0};
switch (in) {
case 0:
@ -272,69 +272,69 @@ namespace skyline::kernel::svc {
default:
state.logger->Debug("svcSleepThread: Thread sleeping for {} ns", in);
struct timespec spec{
.tv_sec = static_cast<time_t>(state.ctx->registers.x0 / 1000000000),
.tv_nsec = static_cast<long>(state.ctx->registers.x0 % 1000000000)
.tv_sec = static_cast<time_t>(state.ctx->gpr.x0 / 1000000000),
.tv_nsec = static_cast<long>(state.ctx->gpr.x0 % 1000000000)
};
nanosleep(&spec, nullptr);
}
}
void GetThreadPriority(DeviceState &state) {
auto handle{state.ctx->registers.w1};
void GetThreadPriority(const DeviceState &state) {
auto handle{state.ctx->gpr.w1};
try {
auto priority{state.process->GetHandle<type::KThread>(handle)->priority};
state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority);
state.ctx->registers.w1 = priority;
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w1 = priority;
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcGetThreadPriority: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void SetThreadPriority(DeviceState &state) {
auto handle{state.ctx->registers.w0};
auto priority{state.ctx->registers.w1};
void SetThreadPriority(const DeviceState &state) {
auto handle{state.ctx->gpr.w0};
auto priority{state.ctx->gpr.w1};
try {
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority);
state.process->GetHandle<type::KThread>(handle)->UpdatePriority(static_cast<u8>(priority));
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void ClearEvent(DeviceState &state) {
auto object{state.process->GetHandle<type::KEvent>(state.ctx->registers.w0)};
void ClearEvent(const DeviceState &state) {
auto object{state.process->GetHandle<type::KEvent>(state.ctx->gpr.w0)};
object->signalled = false;
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void MapSharedMemory(DeviceState &state) {
void MapSharedMemory(const DeviceState &state) {
try {
auto object{state.process->GetHandle<type::KSharedMemory>(state.ctx->registers.w0)};
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x1)};
auto object{state.process->GetHandle<type::KSharedMemory>(state.ctx->gpr.w0)};
auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x1)};
if (!util::PageAligned(pointer)) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcMapSharedMemory: 'pointer' not page aligned: 0x{:X}", pointer);
return;
}
auto size{state.ctx->registers.x2};
auto size{state.ctx->gpr.x2};
if (!util::PageAligned(size)) {
state.ctx->registers.w0 = result::InvalidSize;
state.ctx->gpr.w0 = result::InvalidSize;
state.logger->Warn("svcMapSharedMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->registers.w3)};
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->gpr.w3)};
if ((permission.w && !permission.r) || (permission.x && !permission.r)) {
state.logger->Warn("svcMapSharedMemory: 'permission' invalid: {}{}{}", permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');
state.ctx->registers.w0 = result::InvalidNewMemoryPermission;
state.ctx->gpr.w0 = result::InvalidNewMemoryPermission;
return;
}
@ -342,56 +342,56 @@ namespace skyline::kernel::svc {
object->Map(pointer, size, permission);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
state.ctx->registers.w0 = result::InvalidHandle;
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->gpr.w0);
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void CreateTransferMemory(DeviceState &state) {
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x1)};
void CreateTransferMemory(const DeviceState &state) {
auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x1)};
if (!util::PageAligned(pointer)) {
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcCreateTransferMemory: 'pointer' not page aligned: 0x{:X}", pointer);
return;
}
auto size{state.ctx->registers.x2};
auto size{state.ctx->gpr.x2};
if (!util::PageAligned(size)) {
state.ctx->registers.w0 = result::InvalidSize;
state.ctx->gpr.w0 = result::InvalidSize;
state.logger->Warn("svcCreateTransferMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->registers.w3)};
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->gpr.w3)};
if ((permission.w && !permission.r) || (permission.x && !permission.r)) {
state.logger->Warn("svcCreateTransferMemory: 'permission' invalid: {}{}{}", permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');
state.ctx->registers.w0 = result::InvalidNewMemoryPermission;
state.ctx->gpr.w0 = result::InvalidNewMemoryPermission;
return;
}
auto tmem{state.process->NewHandle<type::KTransferMemory>(pointer, size, permission)};
state.logger->Debug("svcCreateTransferMemory: Creating transfer memory at 0x{:X} for {} bytes ({}{}{})", pointer, size, permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');
state.ctx->registers.w0 = Result{};
state.ctx->registers.w1 = tmem.handle;
state.ctx->gpr.w0 = Result{};
state.ctx->gpr.w1 = tmem.handle;
}
void CloseHandle(DeviceState &state) {
auto handle{static_cast<KHandle>(state.ctx->registers.w0)};
void CloseHandle(const DeviceState &state) {
auto handle{static_cast<KHandle>(state.ctx->gpr.w0)};
try {
state.process->CloseHandle(handle);
state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcCloseHandle: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void ResetSignal(DeviceState &state) {
auto handle{state.ctx->registers.w0};
void ResetSignal(const DeviceState &state) {
auto handle{state.ctx->gpr.w0};
try {
auto object{state.process->GetHandle(handle)};
switch (object->objectType) {
@ -405,32 +405,32 @@ namespace skyline::kernel::svc {
default: {
state.logger->Warn("svcResetSignal: 'handle' type invalid: 0x{:X} ({})", handle, object->objectType);
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
return;
}
}
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} catch (const std::out_of_range &) {
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
return;
}
}
void WaitSynchronization(DeviceState &state) {
void WaitSynchronization(const DeviceState &state) {
constexpr u8 maxSyncHandles{0x40}; // The total amount of handles that can be passed to WaitSynchronization
auto numHandles{state.ctx->registers.w2};
auto numHandles{state.ctx->gpr.w2};
if (numHandles > maxSyncHandles) {
state.ctx->registers.w0 = result::OutOfHandles;
state.ctx->gpr.w0 = result::OutOfHandles;
return;
}
std::string handleStr;
std::vector<std::shared_ptr<type::KSyncObject>> objectTable;
span waitHandles(reinterpret_cast<KHandle*>(state.ctx->registers.x1), numHandles);
span waitHandles(reinterpret_cast<KHandle*>(state.ctx->gpr.x1), numHandles);
for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle);
@ -444,7 +444,7 @@ namespace skyline::kernel::svc {
break;
default: {
state.ctx->registers.w0 = result::InvalidHandle;
state.ctx->gpr.w0 = result::InvalidHandle;
return;
}
}
@ -452,14 +452,14 @@ namespace skyline::kernel::svc {
objectTable.push_back(std::static_pointer_cast<type::KSyncObject>(object));
}
auto timeout{state.ctx->registers.x3};
auto timeout{state.ctx->gpr.x3};
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
auto start{util::GetTimeNs()};
while (true) {
if (state.thread->cancelSync) {
state.thread->cancelSync = false;
state.ctx->registers.w0 = result::Cancelled;
state.ctx->gpr.w0 = result::Cancelled;
break;
}
@ -467,8 +467,8 @@ namespace skyline::kernel::svc {
for (const auto &object : objectTable) {
if (object->signalled) {
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]);
state.ctx->registers.w0 = Result{};
state.ctx->registers.w1 = index;
state.ctx->gpr.w0 = Result{};
state.ctx->gpr.w1 = index;
return;
}
index++;
@ -476,31 +476,31 @@ namespace skyline::kernel::svc {
if ((util::GetTimeNs() - start) >= timeout) {
state.logger->Debug("svcWaitSynchronization: Wait has timed out");
state.ctx->registers.w0 = result::TimedOut;
state.ctx->gpr.w0 = result::TimedOut;
return;
}
}
}
void CancelSynchronization(DeviceState &state) {
void CancelSynchronization(const DeviceState &state) {
try {
state.process->GetHandle<type::KThread>(state.ctx->registers.w0)->cancelSync = true;
state.process->GetHandle<type::KThread>(state.ctx->gpr.w0)->cancelSync = true;
} catch (const std::exception &) {
state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
state.ctx->registers.w0 = result::InvalidHandle;
state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->gpr.w0);
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void ArbitrateLock(DeviceState &state) {
auto pointer{reinterpret_cast<u32*>(state.ctx->registers.x1)};
void ArbitrateLock(const DeviceState &state) {
auto pointer{reinterpret_cast<u32*>(state.ctx->gpr.x1)};
if (!util::WordAligned(pointer)) {
state.logger->Warn("svcArbitrateLock: 'pointer' not word aligned: 0x{:X}", pointer);
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
return;
}
auto ownerHandle{state.ctx->registers.w0};
auto requesterHandle{state.ctx->registers.w2};
auto ownerHandle{state.ctx->gpr.w0};
auto requesterHandle{state.ctx->gpr.w2};
if (requesterHandle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle);
@ -511,14 +511,14 @@ namespace skyline::kernel::svc {
else
state.logger->Debug("svcArbitrateLock: Owner handle did not match current owner for mutex or didn't have waiter flag at 0x{:X}", pointer);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void ArbitrateUnlock(DeviceState &state) {
auto mutex{reinterpret_cast<u32*>(state.ctx->registers.x0)};
void ArbitrateUnlock(const DeviceState &state) {
auto mutex{reinterpret_cast<u32*>(state.ctx->gpr.x0)};
if (!util::WordAligned(mutex)) {
state.logger->Warn("svcArbitrateUnlock: mutex pointer not word aligned: 0x{:X}", mutex);
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
return;
}
@ -526,54 +526,54 @@ namespace skyline::kernel::svc {
if (state.process->MutexUnlock(mutex)) {
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", mutex);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} else {
state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", mutex);
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
}
}
void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mutex{reinterpret_cast<u32*>(state.ctx->registers.x0)};
void WaitProcessWideKeyAtomic(const DeviceState &state) {
auto mutex{reinterpret_cast<u32*>(state.ctx->gpr.x0)};
if (!util::WordAligned(mutex)) {
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex pointer not word aligned: 0x{:X}", mutex);
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
return;
}
auto conditional{reinterpret_cast<void*>(state.ctx->registers.x1)};
auto handle{state.ctx->registers.w2};
auto conditional{reinterpret_cast<void*>(state.ctx->gpr.x1)};
auto handle{state.ctx->gpr.w2};
if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle);
if (!state.process->MutexUnlock(mutex)) {
state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mutex);
state.ctx->registers.w0 = result::InvalidAddress;
state.ctx->gpr.w0 = result::InvalidAddress;
return;
}
auto timeout{state.ctx->registers.x3};
auto timeout{state.ctx->gpr.x3};
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mutex, conditional, timeout);
if (state.process->ConditionalVariableWait(conditional, mutex, timeout)) {
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex");
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
} else {
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
state.ctx->registers.w0 = result::TimedOut;
state.ctx->gpr.w0 = result::TimedOut;
}
}
void SignalProcessWideKey(DeviceState &state) {
auto conditional{reinterpret_cast<void*>(state.ctx->registers.x0)};
auto count{state.ctx->registers.w1};
void SignalProcessWideKey(const DeviceState &state) {
auto conditional{reinterpret_cast<void*>(state.ctx->gpr.x0)};
auto count{state.ctx->gpr.w1};
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", conditional, count);
state.process->ConditionalVariableSignal(conditional, count);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void GetSystemTick(DeviceState &state) {
void GetSystemTick(const DeviceState &state) {
u64 tick;
asm("STR X1, [SP, #-16]!\n\t"
"MRS %0, CNTVCT_EL0\n\t"
@ -583,36 +583,36 @@ namespace skyline::kernel::svc {
"MRS X1, CNTFRQ_EL0\n\t"
"UDIV %0, %0, X1\n\t"
"LDR X1, [SP], #16" : "=r"(tick));
state.ctx->registers.x0 = tick;
state.ctx->gpr.x0 = tick;
}
void ConnectToNamedPort(DeviceState &state) {
void ConnectToNamedPort(const DeviceState &state) {
constexpr u8 portSize = 0x8; //!< The size of a port name string
std::string_view port(span(reinterpret_cast<char*>(state.ctx->registers.x1), portSize).as_string(true));
std::string_view port(span(reinterpret_cast<char*>(state.ctx->gpr.x1), portSize).as_string(true));
KHandle handle{};
if (port.compare("sm:") >= 0) {
handle = state.process->NewHandle<type::KSession>(std::static_pointer_cast<service::BaseService>(state.os->serviceManager.smUserInterface)).handle;
} else {
state.logger->Warn("svcConnectToNamedPort: Connecting to invalid port: '{}'", port);
state.ctx->registers.w0 = result::NotFound;
state.ctx->gpr.w0 = result::NotFound;
return;
}
state.logger->Debug("svcConnectToNamedPort: Connecting to port '{}' at 0x{:X}", port, handle);
state.ctx->registers.w1 = handle;
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w1 = handle;
state.ctx->gpr.w0 = Result{};
}
void SendSyncRequest(DeviceState &state) {
state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->registers.x0));
state.ctx->registers.w0 = Result{};
void SendSyncRequest(const DeviceState &state) {
state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->gpr.x0));
state.ctx->gpr.w0 = Result{};
}
void GetThreadId(DeviceState &state) {
void GetThreadId(const DeviceState &state) {
constexpr KHandle threadSelf{0xFFFF8000}; // The handle used by threads to refer to themselves
auto handle{state.ctx->registers.w1};
auto handle{state.ctx->gpr.w1};
pid_t pid{};
if (handle != threadSelf)
@ -622,24 +622,24 @@ namespace skyline::kernel::svc {
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
state.ctx->registers.x1 = static_cast<u64>(pid);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.x1 = static_cast<u64>(pid);
state.ctx->gpr.w0 = Result{};
}
void OutputDebugString(DeviceState &state) {
auto debug{span(reinterpret_cast<u8*>(state.ctx->registers.x0), state.ctx->registers.x1).as_string()};
void OutputDebugString(const DeviceState &state) {
auto debug{span(reinterpret_cast<u8*>(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()};
if (debug.back() == '\n')
debug.remove_suffix(1);
state.logger->Info("Debug Output: {}", debug);
state.ctx->registers.w0 = Result{};
state.ctx->gpr.w0 = Result{};
}
void GetInfo(DeviceState &state) {
auto id0{state.ctx->registers.w1};
auto handle{state.ctx->registers.w2};
auto id1{state.ctx->registers.x3};
void GetInfo(const DeviceState &state) {
auto id0{state.ctx->gpr.w1};
auto handle{state.ctx->gpr.w2};
auto id1{state.ctx->gpr.x3};
u64 out{};
@ -715,13 +715,13 @@ namespace skyline::kernel::svc {
default:
state.logger->Warn("svcGetInfo: Unimplemented case ID0: {}, ID1: {}", id0, id1);
state.ctx->registers.w0 = result::InvalidEnumValue;
state.ctx->gpr.w0 = result::InvalidEnumValue;
return;
}
state.logger->Debug("svcGetInfo: ID0: {}, ID1: {}, Out: 0x{:X}", id0, id1, out);
state.ctx->registers.x1 = out;
state.ctx->registers.w0 = Result{};
state.ctx->gpr.x1 = out;
state.ctx->gpr.w0 = Result{};
}
}

View File

@ -43,180 +43,180 @@ namespace skyline {
* @brief Sets the process heap to a given size, it can both extend and shrink the heap
* @url https://switchbrew.org/wiki/SVC#SetHeapSize
*/
void SetHeapSize(DeviceState &state);
void SetHeapSize(const DeviceState &state);
/**
* @brief Change attribute of page-aligned memory region, this is used to turn on/off caching for a given memory area
* @url https://switchbrew.org/wiki/SVC#SetMemoryAttribute
*/
void SetMemoryAttribute(DeviceState &state);
void SetMemoryAttribute(const DeviceState &state);
/**
* @brief Maps a memory range into a different range, mainly used for adding guard pages around stack
* @url https://switchbrew.org/wiki/SVC#SetMemoryAttribute
*/
void MapMemory(DeviceState &state);
void MapMemory(const DeviceState &state);
/**
* @brief Unmaps a region that was previously mapped with #MapMemory
* @url https://switchbrew.org/wiki/SVC#UnmapMemory
*/
void UnmapMemory(DeviceState &state);
void UnmapMemory(const DeviceState &state);
/**
* @brief Query information about an address
* @url https://switchbrew.org/wiki/SVC#QueryMemory
*/
void QueryMemory(DeviceState &state);
void QueryMemory(const DeviceState &state);
/**
* @brief Exits the current process
* @url https://switchbrew.org/wiki/SVC#ExitProcess
*/
void ExitProcess(DeviceState &state);
void ExitProcess(const DeviceState &state);
/**
* @brief Create a thread in the current process
* @url https://switchbrew.org/wiki/SVC#CreateThread
*/
void CreateThread(DeviceState &state);
void CreateThread(const DeviceState &state);
/**
* @brief Starts the thread for the provided handle
* @url https://switchbrew.org/wiki/SVC#StartThread
*/
void StartThread(DeviceState &state);
void StartThread(const DeviceState &state);
/**
* @brief Exits the current thread
* @url https://switchbrew.org/wiki/SVC#ExitThread
*/
void ExitThread(DeviceState &state);
void ExitThread(const DeviceState &state);
/**
* @brief Sleep for a specified amount of time, or yield thread
* @url https://switchbrew.org/wiki/SVC#SleepThread
*/
void SleepThread(DeviceState &state);
void SleepThread(const DeviceState &state);
/**
* @brief Get priority of provided thread handle
* @url https://switchbrew.org/wiki/SVC#GetThreadPriority
*/
void GetThreadPriority(DeviceState &state);
void GetThreadPriority(const DeviceState &state);
/**
* @brief Set priority of provided thread handle
* @url https://switchbrew.org/wiki/SVC#SetThreadPriority
*/
void SetThreadPriority(DeviceState &state);
void SetThreadPriority(const DeviceState &state);
/**
* @brief Clears a KEvent of it's signal
* @url https://switchbrew.org/wiki/SVC#ClearEvent
*/
void ClearEvent(DeviceState &state);
void ClearEvent(const DeviceState &state);
/**
* @brief Maps the block supplied by the handle
* @url https://switchbrew.org/wiki/SVC#MapSharedMemory
*/
void MapSharedMemory(DeviceState &state);
void MapSharedMemory(const DeviceState &state);
/**
* @brief Returns a handle to a KSharedMemory object
* @url https://switchbrew.org/wiki/SVC#CreateTransferMemory
*/
void CreateTransferMemory(DeviceState &state);
void CreateTransferMemory(const DeviceState &state);
/**
* @brief Closes the specified handle
* @url https://switchbrew.org/wiki/SVC#CloseHandle
*/
void CloseHandle(DeviceState &state);
void CloseHandle(const DeviceState &state);
/**
* @brief Resets a particular KEvent or KProcess which is signalled
* @url https://switchbrew.org/wiki/SVC#ResetSignal
*/
void ResetSignal(DeviceState &state);
void ResetSignal(const DeviceState &state);
/**
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended
* @url https://switchbrew.org/wiki/SVC#WaitSynchronization
*/
void WaitSynchronization(DeviceState &state);
void WaitSynchronization(const DeviceState &state);
/**
* @brief If the referenced thread is currently in a synchronization call, that call will be interrupted
* @url https://switchbrew.org/wiki/SVC#CancelSynchronization
*/
void CancelSynchronization(DeviceState &state);
void CancelSynchronization(const DeviceState &state);
/**
* @brief Locks a specified mutex
* @url https://switchbrew.org/wiki/SVC#ArbitrateLock
*/
void ArbitrateLock(DeviceState &state);
void ArbitrateLock(const DeviceState &state);
/**
* @brief Unlocks a specified mutex
* @url https://switchbrew.org/wiki/SVC#ArbitrateUnlock
*/
void ArbitrateUnlock(DeviceState &state);
void ArbitrateUnlock(const DeviceState &state);
/**
* @brief Waits on a process-wide key (Conditional-Variable)
* @url https://switchbrew.org/wiki/SVC#WaitProcessWideKeyAtomic
*/
void WaitProcessWideKeyAtomic(DeviceState &state);
void WaitProcessWideKeyAtomic(const DeviceState &state);
/**
* @brief Signals a process-wide key (Conditional-Variable)
* @url https://switchbrew.org/wiki/SVC#SignalProcessWideKey
*/
void SignalProcessWideKey(DeviceState &state);
void SignalProcessWideKey(const DeviceState &state);
/**
* @brief Returns the value of CNTPCT_EL0 on the Switch
* @url https://switchbrew.org/wiki/SVC#GetSystemTick
*/
void GetSystemTick(DeviceState &state);
void GetSystemTick(const DeviceState &state);
/**
* @brief Connects to a named IPC port
* @url https://switchbrew.org/wiki/SVC#ConnectToNamedPort
*/
void ConnectToNamedPort(DeviceState &state);
void ConnectToNamedPort(const DeviceState &state);
/**
* @brief Send a synchronous IPC request to a service
* @url https://switchbrew.org/wiki/SVC#SendSyncRequest
*/
void SendSyncRequest(DeviceState &state);
void SendSyncRequest(const DeviceState &state);
/**
* @brief Retrieves the PID of a specific thread
* @url https://switchbrew.org/wiki/SVC#GetThreadId
*/
void GetThreadId(DeviceState &state);
void GetThreadId(const DeviceState &state);
/**
* @brief Outputs a debug string
* @url https://switchbrew.org/wiki/SVC#OutputDebugString
*/
void OutputDebugString(DeviceState &state);
void OutputDebugString(const DeviceState &state);
/**
* @brief Retrieves a piece of information
* @url https://switchbrew.org/wiki/SVC#GetInfo
*/
void GetInfo(DeviceState &state);
void GetInfo(const DeviceState &state);
/**
* @brief The SVC Table maps all SVCs to their corresponding functions
*/
void static (*SvcTable[0x80])(DeviceState &) = {
static std::array<void (*)(const DeviceState &), 0x80> SvcTable{
nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01
nullptr, // 0x02

View File

@ -54,7 +54,7 @@ namespace skyline::kernel::type {
throw exception("KPrivateMemory permission updated with a non-page-aligned address: 0x{:X}", ptr);
// If a static code region has been mapped as writable it needs to be changed to mutable
if (memState.value == memory::states::CodeStatic.value && permission.w)
if (memState == memory::states::CodeStatic && permission.w)
memState = memory::states::CodeMutable;
state.process->memory.InsertChunk(ChunkDescriptor{

View File

@ -32,7 +32,7 @@ namespace skyline::kernel::type {
void KProcess::InitializeHeap() {
constexpr size_t DefaultHeapSize{0x200000};
heap.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.heap.address), DefaultHeapSize, memory::Permission{true, true, false}, memory::states::Heap);
heap = heap.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.heap.address), DefaultHeapSize, memory::Permission{true, true, false}, memory::states::Heap);
}
u8 *KProcess::AllocateTlsSlot() {

View File

@ -20,27 +20,31 @@ namespace skyline::kernel::type {
void KThread::StartThread() {
pthread_setname_np(pthread_self(), fmt::format("HOS-{}", id).c_str());
ctx.sp = stack->ptr + stack->size;
if (!ctx.tpidrroEl0)
ctx.tpidrroEl0 = parent->AllocateTlsSlot();
ctx.nce = state.nce.get();
ctx.state = &state;
state.ctx = &ctx;
state.thread = shared_from_this();
struct sigaction sigact{
.sa_sigaction = &NCE::SignalHandler,
.sa_sigaction = &nce::NCE::SignalHandler,
.sa_flags = SA_SIGINFO,
};
//for (int signal : {SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV})
// sigaction(signal, &sigact, nullptr);
// sigaction(signal, &sigact, nullptr);
asm volatile(
"MRS X0, TPIDR_EL0\n\t"
"MSR TPIDR_EL0, %x0\n\t" // Set TLS to ThreadContext
"STR X0, [%x0, #0x2F0]\n\t" // Write ThreadContext::hostTpidrEl0
"MOV X0, SP\n\t"
"STR X0, [%x0, #16]\n\t" // Store SP in ThreadContext
"MOV LR, %x1\n\t" // Store entry in Link Register so it is jumped to on return
"MOV X0, %x2\n\t" // Store the argument in X0
"MOV X1, %x3\n\t" // Store the thread handle in X1, NCA applications require this
"STR X0, [%x0, #0x2F8]\n\t" // Write ThreadContext::hostSp
"MOV SP, %x1\n\t" // Replace SP with guest stack
"MOV LR, %x2\n\t" // Store entry in Link Register so it is jumped to on return
"MOV X0, %x3\n\t" // Store the argument in X0
"MOV X1, %x4\n\t" // Store the thread handle in X1, NCA applications require this
"MOV X2, XZR\n\t"
"MOV X3, XZR\n\t"
"MOV X4, XZR\n\t"
@ -105,7 +109,7 @@ namespace skyline::kernel::type {
"DUP V31.16B, WZR\n\t"
"RET"
:
: "r"(&ctx), "r"(entry), "r"(entryArgument), "r"(handle)
: "r"(&ctx), "r"(stack->ptr + stack->size), "r"(entry), "r"(entryArgument), "r"(handle)
: "x0", "x1", "lr"
);

View File

@ -3,6 +3,7 @@
#pragma once
#include <nce/guest.h>
#include "KSyncObject.h"
#include "KPrivateMemory.h"
#include "KSharedMemory.h"
@ -42,7 +43,7 @@ namespace skyline {
/**
* @brief KThread manages a single thread of execution which is responsible for running guest code and kernel code which is invoked by the guest
*/
class KThread : public KSyncObject {
class KThread : public KSyncObject, public std::enable_shared_from_this<KThread> {
private:
KProcess *parent;
std::optional<std::thread> thread; //!< If this KThread is backed by a host thread then this'll hold it
@ -57,7 +58,7 @@ namespace skyline {
size_t id; //!< Index of thread in parent process's KThread vector
std::shared_ptr<KPrivateMemory> stack;
ThreadContext ctx{};
nce::ThreadContext ctx{};
void* entry;
u64 entryArgument;

View File

@ -16,82 +16,66 @@ extern bool Halt;
extern jobject Surface;
extern skyline::GroupMutex JniMtx;
namespace skyline {
void NCE::KernelThread(pid_t thread) {
/*
state.jvm->AttachThread();
namespace skyline::nce {
void NCE::SvcHandler(u16 svc, ThreadContext *ctx) {
const auto &state{*ctx->state};
try {
state.thread = state.process->threads.at(thread);
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->kernel.ptr);
while (true) {
asm("yield");
if (__predict_false(Halt))
break;
if (__predict_false(!Surface))
continue;
if (state.ctx->state == ThreadState::WaitKernel) {
std::lock_guard jniGd(JniMtx);
if (__predict_false(Halt))
break;
if (__predict_false(!Surface))
continue;
auto svc{state.ctx->svc};
try {
if (kernel::svc::SvcTable[svc]) {
state.logger->Debug("SVC called 0x{:X}", svc);
(*kernel::svc::SvcTable[svc])(state);
} else {
throw exception("Unimplemented SVC 0x{:X}", svc);
}
} catch (const std::exception &e) {
throw exception("{} (SVC: 0x{:X})", e.what(), svc);
}
state.ctx->state = ThreadState::WaitRun;
} else if (__predict_false(state.ctx->state == ThreadState::GuestCrash)) {
state.logger->Warn("Thread with PID {} has crashed due to signal: {}", thread, strsignal(state.ctx->svc));
ThreadTrace();
state.ctx->state = ThreadState::WaitRun;
break;
}
auto function{kernel::svc::SvcTable[svc]};
if (function) {
state.logger->Debug("SVC called 0x{:X}", svc);
(*function)(state);
} else {
throw exception("Unimplemented SVC 0x{:X}", svc);
}
} catch (const std::exception &e) {
state.logger->Error(e.what());
} catch (...) {
state.logger->Error("An unknown exception has occurred");
throw exception("{} (SVC: 0x{:X})", e.what(), svc);
// Jumps off the edge?
// Look into this
}
}
void NCE::SignalHandler(int signal, siginfo *, void *context) {
ThreadContext *threadCtx;
asm("MRS %0, TPIDR_EL0":"=r"(threadCtx));
const auto &state{*threadCtx->state};
state.logger->Warn("Thread #{} has crashed due to signal: {}", state.thread->id, strsignal(signal));
std::string raw;
std::string trace;
std::string cpuContext;
const auto &ctx{reinterpret_cast<ucontext *>(context)->uc_mcontext};
constexpr u16 instructionCount{20}; // The amount of previous instructions to print
auto offset{ctx.pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))};
span instructions(reinterpret_cast<u32 *>(offset), instructionCount);
for (auto &instruction : instructions) {
instruction = __builtin_bswap32(instruction);
if (offset == ctx.pc)
trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instruction);
else
trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instruction);
raw += fmt::format("{:08X}", instruction);
offset += sizeof(u32);
}
if (!Halt) {
if (thread == state.process->pid) {
JniMtx.lock(GroupMutex::Group::Group2);
if (ctx.fault_address)
cpuContext += fmt::format("\nFault Address: 0x{:X}", ctx.fault_address);
state.os->KillThread(thread);
Halt = true;
if (ctx.sp)
cpuContext += fmt::format("\nStack Pointer: 0x{:X}", ctx.sp);
JniMtx.unlock();
} else {
state.os->KillThread(thread);
}
}
for (u8 index{}; index < ((sizeof(mcontext_t::regs) / sizeof(u64)) - 2); index += 2)
cpuContext += fmt::format("\n{}X{}: 0x{:<16X} {}{}: 0x{:X}", index < 10 ? ' ' : '\0', index, ctx.regs[index], index < 10 ? ' ' : '\0', index + 1, ctx.regs[index]);
state.jvm->DetachThread();
*/
state.logger->Debug("Process Trace:{}", trace);
state.logger->Debug("Raw Instructions: 0x{}", raw);
state.logger->Debug("CPU Context:{}", cpuContext);
}
NCE::NCE(DeviceState &state) : state(state) {}
NCE::~NCE() {
for (auto &thread : threadMap)
thread.second->join();
}
void NCE::Execute() {
try {
while (true) {
@ -113,265 +97,184 @@ namespace skyline {
}
}
inline ThreadContext *GetContext() {
ThreadContext *ctx;
asm("MRS %0, TPIDR_EL0":"=r"(ctx));
return ctx;
}
void NCE::SignalHandler(int signal, const siginfo &info, const ucontext &context) {
}
void NCE::SignalHandler(int signal, siginfo *info, void *context) {
(GetContext()->nce)->SignalHandler(signal, *info, *reinterpret_cast<ucontext*>(context));
}
void NCE::ThreadTrace(u16 instructionCount, ThreadContext *ctx) {
std::string raw;
std::string trace;
std::string regStr;
ctx = ctx ? ctx : state.ctx;
if (instructionCount) {
auto offset{ctx->pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))};
span instructions(reinterpret_cast<u32 *>(offset), instructionCount);
for (auto &instruction : instructions) {
instruction = __builtin_bswap32(instruction);
if (offset == ctx->pc)
trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instruction);
else
trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instruction);
raw += fmt::format("{:08X}", instruction);
offset += sizeof(u32);
}
}
//if (ctx->faultAddress)
// regStr += fmt::format("\nFault Address: 0x{:X}", ctx->faultAddress);
if (ctx->sp)
regStr += fmt::format("\nStack Pointer: 0x{:X}", ctx->sp);
constexpr u8 numRegisters{31}; //!< The amount of general-purpose registers in ARMv8
for (u8 index{}; index < numRegisters - 2; index += 2) {
auto xStr{index < 10 ? " X" : "X"};
regStr += fmt::format("\n{}{}: 0x{:<16X} {}{}: 0x{:X}", xStr, index, ctx->registers.regs[index], xStr, index + 1, ctx->registers.regs[index + 1]);
}
if (instructionCount) {
state.logger->Debug("Process Trace:{}", trace);
state.logger->Debug("Raw Instructions: 0x{}", raw);
state.logger->Debug("CPU Context:{}", regStr);
} else {
state.logger->Debug("CPU Context:{}", regStr);
}
}
std::vector<u32> NCE::PatchCode(std::vector<u8> &code, u64 baseAddress, i64 offset) {
std::vector<u32> NCE::PatchCode(std::vector<u8> &code, u64 baseAddress, i64 patchBase) {
constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS
constexpr u32 TpidrroEl0{0x5E83}; // ID of TPIDRRO_EL0 in MRS
constexpr u32 CntfrqEl0{0x5F00}; // ID of CNTFRQ_EL0 in MRS
constexpr u32 CntpctEl0{0x5F01}; // ID of CNTPCT_EL0 in MRS
constexpr u32 CntvctEl0{0x5F02}; // ID of CNTVCT_EL0 in MRS
constexpr u32 TegraX1Freq{19200000}; // The clock frequency of the Tegra X1 (19.2 MHz)
constexpr size_t MainSvcTrampolineSize{17};
size_t index{};
std::vector<u32> patch(guest::SaveCtxSize + guest::LoadCtxSize + MainSvcTrampolineSize);
std::memcpy(patch.data(), reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize * sizeof(u32));
index += guest::SaveCtxSize;
{
/* Main SVC Trampoline */
/* Store LR in 16B of pre-allocated stack */
patch[index++] = 0xF90007FE; // STR LR, [SP, #8]
/* Replace Skyline TLS with host TLS */
patch[index++] = 0xD53BD041; // MRS X1, TPIDR_EL0
patch[index++] = 0xF9417822; // LDR X2, [X1, #0x2F0] (ThreadContext::hostTpidrEl0)
/* Replace guest stack with host stack */
patch[index++] = 0xD51BD042; // MSR TPIDR_EL0, X2
patch[index++] = 0x910003E2; // MOV X2, SP
patch[index++] = 0xF9417C23; // LDR X3, [X1, #0x2F8] (ThreadContext::hostSp)
patch[index++] = 0x9100007F; // MOV SP, X3
/* Store Skyline TLS + guest SP on stack */
patch[index++] = 0xA9BF0BE1; // STP X1, X2, [SP, #-16]!
/* Jump to SvcHandler */
for (const auto &mov : instr::MoveRegister(regs::X2, reinterpret_cast<u64>(&NCE::SvcHandler)))
if (mov)
patch[index++] = mov;
patch[index++] = 0xD63F0040; // BLR X2
/* Restore Skyline TLS + guest SP */
patch[index++] = 0xA8C10BE1; // LDP X1, X2, [SP], #16
patch[index++] = 0xD51BD041; // MSR TPIDR_EL0, X1
patch[index++] = 0x9100005F; // MOV SP, X2
/* Restore LR and Return */
patch[index++] = 0xF94007FE; // LDR LR, [SP, #8]
patch[index++] = 0xD65F03C0; // RET
}
std::memcpy(patch.data() + index, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32));
index += guest::LoadCtxSize;
u64 frequency;
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
i64 patchOffset{patchBase / i64(sizeof(u32))};
u32 *start{reinterpret_cast<u32 *>(code.data())};
u32 *end{start + (code.size() / sizeof(u32))};
i64 patchOffset{offset};
for (u32 *instruction{start}; instruction < end; instruction++) {
auto svc{*reinterpret_cast<instr::Svc *>(instruction)};
auto mrs{*reinterpret_cast<instr::Mrs *>(instruction)};
auto msr{*reinterpret_cast<instr::Msr *>(instruction)};
std::vector<u32> patch((guest::SaveCtxSize + guest::LoadCtxSize + guest::SvcHandlerSize) / sizeof(u32));
if (svc.Verify()) {
/* Per-SVC Trampoline */
patch.resize(patch.size() + 7);
std::memcpy(patch.data(), reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize);
offset += guest::SaveCtxSize;
/* Rewrite SVC with B to trampoline */
*instruction = instr::B(patchOffset + index).raw;
std::memcpy(reinterpret_cast<u8 *>(patch.data()) + guest::SaveCtxSize, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize);
offset += guest::LoadCtxSize;
/* Save Context */
patch[index++] = 0xF81F0FFE; // STR LR, [SP, #-16]!
patch[index] = instr::BL(-index).raw;
index++;
std::memcpy(reinterpret_cast<u8 *>(patch.data()) + guest::SaveCtxSize + guest::LoadCtxSize, reinterpret_cast<void *>(&guest::SvcHandler), guest::SvcHandlerSize);
offset += guest::SvcHandlerSize;
/* Jump to main SVC trampoline */
patch[index++] = instr::Movz(regs::W0, static_cast<u16>(svc.value)).raw;
patch[index] = instr::BL(guest::SaveCtxSize - index).raw;
index++;
static u64 frequency{};
if (!frequency)
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
/* Restore Context and Return */
patch[index] = instr::BL(guest::SaveCtxSize + MainSvcTrampolineSize - index).raw;
index++;
patch[index++] = 0xF84107FE; // LDR LR, [SP], #16
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
index++;
} else if (mrs.Verify()) {
if (mrs.srcReg == TpidrroEl0 || mrs.srcReg == TpidrEl0) {
/* Emulated TLS Register Load */
patch.resize(patch.size() + ((mrs.destReg != regs::X0) ? 6 : 3));
for (u32 *address{start}; address < end; address++) {
auto instrSvc{reinterpret_cast<instr::Svc *>(address)};
auto instrMrs{reinterpret_cast<instr::Mrs *>(address)};
auto instrMsr{reinterpret_cast<instr::Msr *>(address)};
/* Rewrite MRS with B to trampoline */
*instruction = instr::B(patchOffset + index).raw;
if (instrSvc->Verify()) {
// If this is an SVC we need to branch to saveCtx then to the SVC Handler after putting the PC + SVC into X0 and W1 and finally loadCtx before returning to where we were before
instr::B bJunc(offset);
/* Allocate Scratch Register */
if (mrs.destReg != regs::X0)
patch[index++] = 0xF81F0FE0; // STR X0, [SP, #-16]!
constexpr u32 strLr{0xF81F0FFE}; // STR LR, [SP, #-16]!
offset += sizeof(strLr);
instr::BL bSvCtx(patchOffset - offset);
offset += sizeof(bSvCtx);
auto movPc{instr::MoveRegister<u64>(regs::X0, baseAddress + (address - start))};
offset += sizeof(u32) * movPc.size();
instr::Movz movCmd(regs::W1, static_cast<u16>(instrSvc->value));
offset += sizeof(movCmd);
instr::BL bSvcHandler((patchOffset + guest::SaveCtxSize + guest::LoadCtxSize) - offset);
offset += sizeof(bSvcHandler);
instr::BL bLdCtx((patchOffset + guest::SaveCtxSize) - offset);
offset += sizeof(bLdCtx);
constexpr u32 ldrLr{0xF84107FE}; // LDR LR, [SP], #16
offset += sizeof(ldrLr);
instr::B bret(-offset + sizeof(u32));
offset += sizeof(bret);
*address = bJunc.raw;
patch.push_back(strLr);
patch.push_back(bSvCtx.raw);
for (auto &instr : movPc)
patch.push_back(instr);
patch.push_back(movCmd.raw);
patch.push_back(bSvcHandler.raw);
patch.push_back(bLdCtx.raw);
patch.push_back(ldrLr);
patch.push_back(bret.raw);
} else if (instrMrs->Verify()) {
if (instrMrs->srcReg == TpidrroEl0 || instrMrs->srcReg == TpidrEl0) {
// If this moves TPIDR(RO)_EL0 into a register then we retrieve the value of our virtual TPIDR(RO)_EL0 from TLS and write it to the register
instr::B bJunc(offset);
u32 strX0{};
if (instrMrs->destReg != regs::X0) {
strX0 = 0xF81F0FE0; // STR X0, [SP, #-16]!
offset += sizeof(strX0);
}
constexpr u32 mrsX0{0xD53BD040}; // MRS X0, TPIDR_EL0
offset += sizeof(mrsX0);
u32 ldrTls;
if (instrMrs->srcReg == TpidrroEl0)
ldrTls = 0xF9408000; // LDR X0, [X0, #256] (ThreadContext::tpidrroEl0)
/* Retrieve emulated TLS register from ThreadContext */
patch[index++] = 0xD53BD040; // MRS X0, TPIDR_EL0
if (mrs.srcReg == TpidrroEl0)
patch[index++] = 0xF9418000; // LDR X0, [X0, #0x300] (ThreadContext::tpidrroEl0)
else
ldrTls = 0xF9408400; // LDR X0, [X0, #264] (ThreadContext::tpidrEl0)
patch[index++] = 0xF9418400; // LDR X0, [X0, #0x308] (ThreadContext::tpidrEl0)
offset += sizeof(ldrTls);
u32 movXn{};
u32 ldrX0{};
if (instrMrs->destReg != regs::X0) {
movXn = instr::Mov(regs::X(instrMrs->destReg), regs::X0).raw;
offset += sizeof(movXn);
ldrX0 = 0xF84107E0; // LDR X0, [SP], #16
offset += sizeof(ldrX0);
}
instr::B bret(-offset + sizeof(u32));
offset += sizeof(bret);
*address = bJunc.raw;
if (strX0)
patch.push_back(strX0);
patch.push_back(mrsX0);
patch.push_back(ldrTls);
if (movXn)
patch.push_back(movXn);
if (ldrX0)
patch.push_back(ldrX0);
patch.push_back(bret.raw);
} else if (frequency != TegraX1Freq) {
// These deal with changing the timer registers, we only do this if the clock frequency doesn't match the X1's clock frequency
if (instrMrs->srcReg == CntpctEl0) {
// If this moves CNTPCT_EL0 into a register then call RescaleClock to rescale the device's clock to the X1's clock frequency and write result to register
instr::B bJunc(offset);
offset += guest::RescaleClockSize;
instr::Ldr ldr(0xF94003E0); // LDR XOUT, [SP]
ldr.destReg = instrMrs->destReg;
offset += sizeof(ldr);
constexpr u32 addSp{0x910083FF}; // ADD SP, SP, #32
offset += sizeof(addSp);
instr::B bret(-offset + sizeof(u32));
offset += sizeof(bret);
*address = bJunc.raw;
auto size{patch.size()};
patch.resize(size + (guest::RescaleClockSize / sizeof(u32)));
std::memcpy(patch.data() + size, reinterpret_cast<void *>(&guest::RescaleClock), guest::RescaleClockSize);
patch.push_back(ldr.raw);
patch.push_back(addSp);
patch.push_back(bret.raw);
} else if (instrMrs->srcReg == CntfrqEl0) {
// If this moves CNTFRQ_EL0 into a register then move the Tegra X1's clock frequency into the register (Rather than the host clock frequency)
instr::B bJunc(offset);
auto movFreq{instr::MoveRegister<u32>(static_cast<regs::X>(instrMrs->destReg), TegraX1Freq)};
offset += sizeof(u32) * movFreq.size();
instr::B bret(-offset + sizeof(u32));
offset += sizeof(bret);
*address = bJunc.raw;
for (auto &instr : movFreq)
patch.push_back(instr);
patch.push_back(bret.raw);
/* Restore Scratch Register and Return */
if (mrs.destReg != regs::X0) {
patch[index++] = instr::Mov(regs::X(mrs.destReg), regs::X0).raw;
patch[index++] = 0xF84107E0; // LDR X0, [SP], #16
}
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
index++;
} else {
// If the host clock frequency is the same as the Tegra X1's clock frequency
if (instrMrs->srcReg == CntpctEl0) {
// If this moves CNTPCT_EL0 into a register, change the instruction to move CNTVCT_EL0 instead as Linux or most other OSes don't allow access to CNTPCT_EL0 rather only CNTVCT_EL0 can be accessed from userspace
*address = instr::Mrs(CntvctEl0, regs::X(instrMrs->destReg)).raw;
if (frequency != TegraX1Freq) {
if (mrs.srcReg == CntpctEl0) {
/* Physical Counter Load Emulation (With Rescaling) */
patch.resize(patch.size() + guest::RescaleClockSize + 3);
/* Rewrite MRS with B to trampoline */
*instruction = instr::B(patchOffset + index).raw;
/* Rescale host clock */
std::memcpy(patch.data() + index, reinterpret_cast<void *>(&guest::RescaleClock), guest::RescaleClockSize);
index += guest::RescaleClockSize;
/* Load result from stack into destination register */
instr::Ldr ldr(0xF94003E0); // LDR XOUT, [SP]
ldr.destReg = mrs.destReg;
patch[index++] = ldr.raw;
/* Free 32B stack allocation by RescaleClock and Return */
patch[index++] = {0x910083FF}; // ADD SP, SP, #32
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
index++;
} else if (mrs.srcReg == CntfrqEl0) {
/* Physical Counter Frequency Load Emulation */
patch.resize(patch.size() + 3);
/* Rewrite MRS with B to trampoline */
*instruction = instr::B(patchOffset + index).raw;
/* Write back Tegra X1 Counter Frequency and Return */
for (const auto &mov : instr::MoveRegister(regs::X(mrs.destReg), TegraX1Freq))
patch[index++] = mov;
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
index++;
}
} else if (mrs.srcReg == CntpctEl0) {
/* Physical Counter Load Emulation (Without Rescaling) */
// We just convert CNTPCT_EL0 -> CNTVCT_EL0 as Linux doesn't allow access to the physical counter
*instruction = instr::Mrs(CntvctEl0, regs::X(mrs.destReg)).raw;
}
}
} else if (instrMsr->Verify()) {
if (instrMsr->destReg == TpidrEl0) {
// If this moves a register into TPIDR_EL0 then we retrieve the value of the register and write it to our virtual TPIDR_EL0 in TLS
instr::B bJunc(offset);
} else if (msr.Verify()) {
if (msr.destReg == TpidrEl0) {
/* Emulated TLS Register Store */
patch.resize(patch.size() + 6);
// Used to avoid conflicts as we cannot read the source register from the stack
bool x0x1{instrMrs->srcReg != regs::X0 && instrMrs->srcReg != regs::X1};
/* Rewrite MSR with B to trampoline */
*instruction = instr::B(patchOffset + index).raw;
// Push two registers to stack that can be used to load the TLS and arguments into
u32 pushXn{x0x1 ? 0xA9BF07E0 : 0xA9BF0FE2}; // STP X(0/2), X(1/3), [SP, #-16]!
offset += sizeof(pushXn);
/* Allocate Scratch Registers */
bool x0x1{mrs.srcReg != regs::X0 && mrs.srcReg != regs::X1};
patch[index++] = x0x1 ? 0xA9BF07E0 : 0xA9BF0FE2; // STP X(0/2), X(1/3), [SP, #-16]!
u32 loadRealTls{x0x1 ? 0xD53BD040 : 0xD53BD042}; // MRS X(0/2), TPIDR_EL0
offset += sizeof(loadRealTls);
/* Store new TLS value into ThreadContext */
patch[index++] = x0x1 ? 0xD53BD040 : 0xD53BD042; // MRS X(0/2), TPIDR_EL0
patch[index++] = instr::Mov(x0x1 ? regs::X1 : regs::X3, regs::X(msr.srcReg)).raw;
patch[index++] = x0x1 ? 0xF9018401 : 0xF9018403; // STR X(1/3), [X0, #0x308] (ThreadContext::tpidrEl0)
instr::Mov moveParam(x0x1 ? regs::X1 : regs::X3, regs::X(instrMsr->srcReg));
offset += sizeof(moveParam);
u32 storeEmuTls{x0x1 ? 0xF9008401 : 0xF9008403}; // STR X(1/3), [X0, #264] (ThreadContext::tpidrEl0)
offset += sizeof(storeEmuTls);
u32 popXn{x0x1 ? 0xA8C107E0 : 0xA8C10FE2}; // LDP X(0/2), X(1/3), [SP], #16
offset += sizeof(popXn);
instr::B bret(-offset + sizeof(u32));
offset += sizeof(bret);
*address = bJunc.raw;
patch.push_back(pushXn);
patch.push_back(loadRealTls);
patch.push_back(moveParam.raw);
patch.push_back(storeEmuTls);
patch.push_back(popXn);
patch.push_back(bret.raw);
/* Restore Scratch Registers and Return */
patch[index++] = x0x1 ? 0xA8C107E0 : 0xA8C10FE2; // LDP X(0/2), X(1/3), [SP], #16
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
index++;
}
}
offset -= sizeof(u32);
patchOffset -= sizeof(u32);
patchOffset--;
}
return patch;
}

View File

@ -6,47 +6,28 @@
#include "common.h"
#include <sys/wait.h>
namespace skyline {
namespace skyline::nce {
/**
* @brief The NCE (Native Code Execution) class is responsible for managing the state of catching instructions and directly controlling processes/threads
*/
class NCE {
private:
DeviceState &state;
std::unordered_map<pid_t, std::shared_ptr<std::thread>> threadMap; //!< This maps all of the host threads to their corresponding kernel thread
/**
* @brief The event loop of a kernel thread managing a guest thread
* @param thread The PID of the thread to manage
*/
void KernelThread(pid_t thread);
static void SvcHandler(u16 svc, ThreadContext* ctx);
public:
NCE(DeviceState &state);
static void SignalHandler(int signal, siginfo *info, void *context);
~NCE();
NCE(DeviceState &state);
void Execute();
void SignalHandler(int signal, const siginfo &info, const ucontext &ucontext);
/**
* @brief A delegator to the real signal handler after restoring context
*/
static void SignalHandler(int signal, siginfo *info, void *context);
/**
* @brief Prints out a trace and the CPU context
* @param instructionCount The amount of previous instructions to print (Can be 0)
*/
void ThreadTrace(u16 instructionCount = 10, ThreadContext *ctx = nullptr);
/**
* @brief Patches specific parts of the code
* @param code A vector with the code to be patched
* @brief Generates a patch section for the supplied code
* @param baseAddress The address at which the code is mapped
* @param offset The offset of the code block from the base address
* @param patchBase The offset of the patch section from the base address
*/
std::vector<u32> PatchCode(std::vector<u8> &code, u64 baseAddress, i64 offset);
std::vector<u32> PatchCode(std::vector<u8> &code, u64 baseAddress, i64 patchBase);
};
}

View File

@ -4,46 +4,104 @@
.text
.global SaveCtx
SaveCtx:
STR LR, [SP, #-16]!
/* Prepare Scratch Register */
STR LR, [SP, #8] // It is assumed that 8B of stack memory has already been allocated before calling this
MRS LR, TPIDR_EL0
STP X0, X1, [LR, #16]
STP X2, X3, [LR, #32]
STP X4, X5, [LR, #48]
STP X6, X7, [LR, #64]
STP X8, X9, [LR, #80]
STP X10, X11, [LR, #96]
STP X12, X13, [LR, #112]
STP X14, X15, [LR, #128]
STP X16, X17, [LR, #144]
STP X18, X19, [LR, #160]
STP X20, X21, [LR, #176]
STP X22, X23, [LR, #192]
STP X24, X25, [LR, #208]
STP X26, X27, [LR, #224]
STP X28, X29, [LR, #240]
LDR LR, [SP], #16
/* Store GP Registers */
STP X0, X1, [LR, #0]
STP X2, X3, [LR, #16]
STP X4, X5, [LR, #32]
STP X6, X7, [LR, #48]
STP X8, X9, [LR, #64]
STP X10, X11, [LR, #80]
STP X12, X13, [LR, #96]
STP X14, X15, [LR, #112]
STP X16, X17, [LR, #128]
STP X18, X19, [LR, #144]
STP X20, X21, [LR, #160]
STP X22, X23, [LR, #176]
STP X24, X25, [LR, #192]
STP X26, X27, [LR, #208]
STP X28, X29, [LR, #224]
/* Store FP Registers */
STP Q0, Q1, [LR, #240]
STP Q2, Q3, [LR, #272]
STP Q4, Q5, [LR, #304]
STP Q6, Q7, [LR, #336]
STP Q8, Q9, [LR, #368]
STP Q10, Q11, [LR, #400]
STP Q12, Q13, [LR, #432]
STP Q14, Q15, [LR, #464]
STP Q16, Q17, [LR, #496]
STP Q18, Q19, [LR, #528]
STP Q20, Q21, [LR, #560]
STP Q22, Q23, [LR, #592]
STP Q24, Q25, [LR, #624]
STP Q26, Q27, [LR, #656]
STP Q28, Q29, [LR, #688]
STP Q30, Q31, [LR, #720]
/* Store FPCR/FPSR */
MRS X0, FPSR
STR W0, [LR, #744]
MRS X1, FPCR
STR W1, [LR, #748]
/* Restore Scratch Register */
LDR LR, [SP, #8]
RET
.global LoadCtx
LoadCtx:
STR LR, [SP, #-16]!
/* Prepare Scratch Register */
STR LR, [SP, #8] // It is assumed that 8B of stack memory has already been allocated before calling this
MRS LR, TPIDR_EL0
LDP X0, X1, [LR, #16]
LDP X2, X3, [LR, #32]
LDP X4, X5, [LR, #48]
LDP X6, X7, [LR, #64]
LDP X8, X9, [LR, #80]
LDP X10, X11, [LR, #96]
LDP X12, X13, [LR, #112]
LDP X14, X15, [LR, #128]
LDP X16, X17, [LR, #144]
LDP X18, X19, [LR, #160]
LDP X20, X21, [LR, #176]
LDP X22, X23, [LR, #192]
LDP X24, X25, [LR, #208]
LDP X26, X27, [LR, #224]
LDP X28, X29, [LR, #240]
LDR LR, [SP], #16
/* Load FP Registers */
LDP Q0, Q1, [LR, #240]
LDP Q2, Q3, [LR, #272]
LDP Q4, Q5, [LR, #304]
LDP Q6, Q7, [LR, #336]
LDP Q8, Q9, [LR, #368]
LDP Q10, Q11, [LR, #400]
LDP Q12, Q13, [LR, #432]
LDP Q14, Q15, [LR, #464]
LDP Q16, Q17, [LR, #496]
LDP Q18, Q19, [LR, #528]
LDP Q20, Q21, [LR, #560]
LDP Q22, Q23, [LR, #592]
LDP Q24, Q25, [LR, #624]
LDP Q26, Q27, [LR, #656]
LDP Q28, Q29, [LR, #688]
LDP Q30, Q31, [LR, #720]
/* Store FPCR/FPSR */
MRS X0, FPSR
STR W0, [LR, #744]
MRS X1, FPCR
STR W1, [LR, #748]
/* Load GP Registers */
LDP X0, X1, [LR, #0]
LDP X2, X3, [LR, #16]
LDP X4, X5, [LR, #32]
LDP X6, X7, [LR, #48]
LDP X8, X9, [LR, #64]
LDP X10, X11, [LR, #80]
LDP X12, X13, [LR, #96]
LDP X14, X15, [LR, #112]
LDP X16, X17, [LR, #128]
LDP X18, X19, [LR, #144]
LDP X20, X21, [LR, #160]
LDP X22, X23, [LR, #176]
LDP X24, X25, [LR, #192]
LDP X26, X27, [LR, #208]
LDP X28, X29, [LR, #224]
/* Restore Scratch Register */
LDR LR, [SP, #8]
RET
.global RescaleClock

View File

@ -3,39 +3,160 @@
#pragma once
#include "guest_common.h"
#include <common.h>
namespace skyline {
namespace guest {
constexpr size_t SaveCtxSize{20 * sizeof(u32)}; //!< The size of the SaveCtx function in 32-bit ARMv8 instructions
constexpr size_t LoadCtxSize{20 * sizeof(u32)}; //!< The size of the LoadCtx function in 32-bit ARMv8 instructions
constexpr size_t RescaleClockSize{16 * sizeof(u32)}; //!< The size of the RescaleClock function in 32-bit ARMv8 instructions
#ifdef NDEBUG
constexpr size_t SvcHandlerSize{225 * sizeof(u32)}; //!< The size of the SvcHandler (Release) function in 32-bit ARMv8 instructions
#else
constexpr size_t SvcHandlerSize{400 * sizeof(u32)}; //!< The size of the SvcHandler (Debug) function in 32-bit ARMv8 instructions
#endif
struct DeviceState;
namespace nce {
/**
* @brief The state of all the general purpose registers in the guest
* @note Read about ARMv8 registers here: https://developer.arm.com/architectures/learn-the-architecture/armv8-a-instruction-set-architecture/registers-in-aarch64-general-purpose-registers
* @note X30 or LR is not provided as it is reserved for other uses
*/
union GpRegisters {
std::array<u64, 30> regs;
struct {
u64 x0;
u64 x1;
u64 x2;
u64 x3;
u64 x4;
u64 x5;
u64 x6;
u64 x7;
u64 x8;
u64 x9;
u64 x10;
u64 x11;
u64 x12;
u64 x13;
u64 x14;
u64 x15;
u64 x16;
u64 x17;
u64 x18;
u64 x19;
u64 x20;
u64 x21;
u64 x22;
u64 x23;
u64 x24;
u64 x25;
u64 x26;
u64 x27;
u64 x28;
u64 x29;
};
struct {
u32 w0;
u32 __w0__;
u32 w1;
u32 __w1__;
u32 w2;
u32 __w2__;
u32 w3;
u32 __w3__;
u32 w4;
u32 __w4__;
u32 w5;
u32 __w5__;
u32 w6;
u32 __w6__;
u32 w7;
u32 __w7__;
u32 w8;
u32 __w8__;
u32 w9;
u32 __w9__;
u32 w10;
u32 __w10__;
u32 w11;
u32 __w11__;
u32 w12;
u32 __w12__;
u32 w13;
u32 __w13__;
u32 w14;
u32 __w14__;
u32 w15;
u32 __w15__;
u32 w16;
u32 __w16__;
u32 w17;
u32 __w17__;
u32 w18;
u32 __w18__;
u32 w19;
u32 __w19__;
u32 w20;
u32 __w20__;
u32 w21;
u32 __w21__;
u32 w22;
u32 __w22__;
u32 w23;
u32 __w23__;
u32 w24;
u32 __w24__;
u32 w25;
u32 __w25__;
u32 w26;
u32 __w26__;
u32 w27;
u32 __w27__;
u32 w28;
u32 __w28__;
u32 w29;
u32 __w29__;
};
};
/**
* @brief Saves the context from CPU registers into TLS
* @brief The state of all the floating point (and SIMD) registers in the guest
* @note FPSR/FPCR are 64-bit system registers but only the lower 32-bits are used
*/
extern "C" void SaveCtx(void);
union FpRegisters {
std::array<u128, 32> regs;
u32 fpsr;
u32 fpcr;
};
/**
* @brief Loads the context from TLS into CPU registers
* @brief A per-thread context for guest threads
* @note It's stored in TPIDR_EL0 while running the guest
*/
extern "C" void LoadCtx(void);
struct ThreadContext {
GpRegisters gpr;
FpRegisters fpr;
u8 *hostTpidrEl0; //!< Host TLS TPIDR_EL0, this must be swapped to prior to calling any CXX functions
u8 *hostSp; //!< Host Stack Pointer, same as above
u8 *tpidrroEl0; //!< Emulated HOS TPIDRRO_EL0
u8 *tpidrEl0; //!< Emulated HOS TPIDR_EL0
const DeviceState *state;
};
/**
* @brief Rescales the clock to Tegra X1 levels and puts the output on stack
*/
extern "C" __noreturn void RescaleClock(void);
namespace guest {
constexpr size_t SaveCtxSize{39}; //!< The size of the SaveCtx function in 32-bit ARMv8 instructions
constexpr size_t LoadCtxSize{39}; //!< The size of the LoadCtx function in 32-bit ARMv8 instructions
constexpr size_t RescaleClockSize{16}; //!< The size of the RescaleClock function in 32-bit ARMv8 instructions
/**
* @brief Handles all SVC calls
* @param pc The address of PC when the call was being done
* @param svc The SVC ID of the SVC being called
*/
void SvcHandler(u64 pc, u16 svc);
/**
* @brief Saves the context from CPU registers into TLS
* @note Assumes that 8B is reserved at an offset of 8B from SP
*/
extern "C" void SaveCtx(void);
/**
* @brief Loads the context from TLS into CPU registers
* @note Assumes that 8B is reserved at an offset of 8B from SP
*/
extern "C" void LoadCtx(void);
/**
* @brief Rescales the host clock to Tegra X1 levels
* @note Output is on stack with the stack pointer offset 32B from the initial point
*/
extern "C" __noreturn void RescaleClock(void);
}
}
}

View File

@ -1,138 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <cstdint>
#define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage)
namespace skyline {
using u128 = __uint128_t; //!< Unsigned 128-bit integer
using u64 = __uint64_t; //!< Unsigned 64-bit integer
using u32 = __uint32_t; //!< Unsigned 32-bit integer
using u16 = __uint16_t; //!< Unsigned 16-bit integer
using u8 = __uint8_t; //!< Unsigned 8-bit integer
using i128 = __int128_t; //!< Signed 128-bit integer
using i64 = __int64_t; //!< Signed 64-bit integer
using i32 = __int32_t; //!< Signed 32-bit integer
using i16 = __int16_t; //!< Signed 16-bit integer
using i8 = __int8_t; //!< Signed 8-bit integer
/**
* @brief The state of all the general purpose registers in the guest
* @note Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
* @note X30 or LR is not provided as it is reserved for other uses
*/
union Registers {
u64 regs[30];
struct {
u64 x0;
u64 x1;
u64 x2;
u64 x3;
u64 x4;
u64 x5;
u64 x6;
u64 x7;
u64 x8;
u64 x9;
u64 x10;
u64 x11;
u64 x12;
u64 x13;
u64 x14;
u64 x15;
u64 x16;
u64 x17;
u64 x18;
u64 x19;
u64 x20;
u64 x21;
u64 x22;
u64 x23;
u64 x24;
u64 x25;
u64 x26;
u64 x27;
u64 x28;
u64 x29;
};
struct {
u32 w0;
u32 __w0__;
u32 w1;
u32 __w1__;
u32 w2;
u32 __w2__;
u32 w3;
u32 __w3__;
u32 w4;
u32 __w4__;
u32 w5;
u32 __w5__;
u32 w6;
u32 __w6__;
u32 w7;
u32 __w7__;
u32 w8;
u32 __w8__;
u32 w9;
u32 __w9__;
u32 w10;
u32 __w10__;
u32 w11;
u32 __w11__;
u32 w12;
u32 __w12__;
u32 w13;
u32 __w13__;
u32 w14;
u32 __w14__;
u32 w15;
u32 __w15__;
u32 w16;
u32 __w16__;
u32 w17;
u32 __w17__;
u32 w18;
u32 __w18__;
u32 w19;
u32 __w19__;
u32 w20;
u32 __w20__;
u32 w21;
u32 __w21__;
u32 w22;
u32 __w22__;
u32 w23;
u32 __w23__;
u32 w24;
u32 __w24__;
u32 w25;
u32 __w25__;
u32 w26;
u32 __w26__;
u32 w27;
u32 __w27__;
u32 w28;
u32 __w28__;
u32 w29;
u32 __w29__;
};
};
class NCE;
/**
* @brief The context of a thread during kernel calls, it is stored for each thread
*/
struct ThreadContext {
u8* pc; //!< The program counter on the guest
u8* sp; //!< The stack pointer on the guest
Registers registers; //!< The general purpose registers on the guest
u8* tpidrroEl0; //!< The value for TPIDRRO_EL0 for the current thread
u8* tpidrEl0; //!< The value for TPIDR_EL0 for the current thread
NCE* nce; //!< An instance of the NCE class, used by trampoline functions to call class methods
};
}

View File

@ -3,7 +3,7 @@
#include <common.h>
namespace skyline {
namespace skyline::nce {
namespace regs {
enum X { X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30 };
enum W { W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, W16, W17, W18, W19, W20, W21, W22, W23, W24, W25, W26, W27, W28, W29, W30 };
@ -112,10 +112,10 @@ namespace skyline {
struct B {
public:
/**
* @param offset The relative offset to branch to (Should be 32-bit aligned)
* @param offset The relative offset to branch to (In 32-bit units)
*/
constexpr B(i64 offset) {
this->offset = static_cast<i32>(offset / sizeof(u32));
constexpr B(i32 offset) {
this->offset = offset;
sig = 0x5;
}
@ -146,10 +146,10 @@ namespace skyline {
struct BL {
public:
/**
* @param offset The relative offset to branch to (Should be 32-bit aligned)
* @param offset The relative offset to branch to (In 32-bit units)
*/
constexpr BL(i64 offset) {
this->offset = static_cast<i32>(offset / sizeof(u32));
constexpr BL(i32 offset) {
this->offset = offset;
sig = 0x25;
}
@ -288,20 +288,26 @@ namespace skyline {
* @param destination The destination register of the operation
* @param value The value to insert into the register
* @return A array with the instructions to insert the value
* @note 0 is returned for any instruction that isn't required
*/
template<typename Type>
constexpr std::array<u32, sizeof(Type) / sizeof(u16)> MoveRegister(regs::X destination, Type value) {
std::array<u32, sizeof(Type) / sizeof(u16)> instructions;
auto valuePointer{reinterpret_cast<u16 *>(&value)};
bool zeroed{};
u8 offset{};
for (auto &instruction : instructions) {
if (offset)
instruction = instr::Movk(destination, *(valuePointer + offset), offset).raw;
else
instruction = instr::Movz(destination, *(valuePointer + offset), offset).raw;
auto offsetValue{*(valuePointer + offset)};
if (offsetValue) {
if (zeroed) {
instruction = instr::Movk(destination, offsetValue, offset).raw;
} else {
instruction = instr::Movz(destination, offsetValue, offset).raw;
zeroed = true;
}
}
offset++;
}