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}/loader_jni.cpp
${source_DIR}/skyline/common.cpp ${source_DIR}/skyline/common.cpp
${source_DIR}/skyline/nce/guest.S ${source_DIR}/skyline/nce/guest.S
${source_DIR}/skyline/nce/guest.cpp
${source_DIR}/skyline/nce.cpp ${source_DIR}/skyline/nce.cpp
${source_DIR}/skyline/jvm.cpp ${source_DIR}/skyline/jvm.cpp
${source_DIR}/skyline/audio.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) 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) { : 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 // 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); gpu = std::make_shared<gpu::GPU>(*this);
audio = std::make_shared<audio::Audio>(*this); audio = std::make_shared<audio::Audio>(*this);
input = std::make_shared<input::Input>(*this); input = std::make_shared<input::Input>(*this);
} }
thread_local std::shared_ptr<kernel::type::KThread> DeviceState::thread = nullptr; 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/unordered_map.h>
#include <frozen/string.h> #include <frozen/string.h>
#include <jni.h> #include <jni.h>
#include "nce/guest_common.h"
#define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage)
namespace skyline { 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 using KHandle = u32; //!< The type of a kernel handle
namespace frz = frozen;
/** /**
* @brief The result of an operation in HOS * @brief The result of an operation in HOS
@ -105,8 +117,7 @@ namespace skyline {
* @return The current time in nanoseconds * @return The current time in nanoseconds
*/ */
inline u64 GetTimeNs() { inline u64 GetTimeNs() {
static u64 frequency{}; u64 frequency;
if (!frequency)
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency)); asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
u64 ticks; u64 ticks;
asm("MRS %0, CNTVCT_EL0" : "=r"(ticks)); asm("MRS %0, CNTVCT_EL0" : "=r"(ticks));
@ -529,6 +540,10 @@ namespace skyline {
void List(const std::shared_ptr<Logger> &logger); void List(const std::shared_ptr<Logger> &logger);
}; };
namespace nce {
class NCE;
struct ThreadContext;
}
class JvmManager; class JvmManager;
namespace gpu { namespace gpu {
class GPU; class GPU;
@ -559,8 +574,8 @@ namespace skyline {
kernel::OS *os; kernel::OS *os;
std::shared_ptr<kernel::type::KProcess> &process; 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 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 thread_local static nce::ThreadContext *ctx; //!< The context of the guest thread for the corresponding host thread
std::shared_ptr<NCE> nce; std::shared_ptr<nce::NCE> nce;
std::shared_ptr<gpu::GPU> gpu; std::shared_ptr<gpu::GPU> gpu;
std::shared_ptr<audio::Audio> audio; std::shared_ptr<audio::Audio> audio;
std::shared_ptr<input::Input> input; std::shared_ptr<input::Input> input;

View File

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

View File

@ -108,6 +108,14 @@ namespace skyline {
constexpr MemoryState() : value(0) {} 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 { struct {
MemoryType type; MemoryType type;
bool permissionChangeAllowed : 1; //!< If the application can use svcSetMemoryPermission on this block bool permissionChangeAllowed : 1; //!< If the application can use svcSetMemoryPermission on this block

View File

@ -7,12 +7,12 @@
#include "svc.h" #include "svc.h"
namespace skyline::kernel::svc { namespace skyline::kernel::svc {
void SetHeapSize(DeviceState &state) { void SetHeapSize(const DeviceState &state) {
auto size{state.ctx->registers.w1}; auto size{state.ctx->gpr.w1};
if (!util::IsAligned(size, 0x200000)) { if (!util::IsAligned(size, 0x200000)) {
state.ctx->registers.w0 = result::InvalidSize; state.ctx->gpr.w0 = result::InvalidSize;
state.ctx->registers.x1 = 0; state.ctx->gpr.x1 = 0;
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size); state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
return; return;
@ -21,46 +21,46 @@ namespace skyline::kernel::svc {
auto &heap{state.process->heap}; auto &heap{state.process->heap};
heap->Resize(size); heap->Resize(size);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
state.ctx->registers.x1 = reinterpret_cast<u64>(heap->ptr); 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) { void SetMemoryAttribute(const DeviceState &state) {
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x0)}; auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x0)};
if (!util::PageAligned(pointer)) { 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); state.logger->Warn("svcSetMemoryAttribute: 'pointer' not page aligned: 0x{:X}", pointer);
return; return;
} }
auto size{state.ctx->registers.x1}; auto size{state.ctx->gpr.x1};
if (!util::PageAligned(size)) { 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); state.logger->Warn("svcSetMemoryAttribute: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; return;
} }
memory::MemoryAttribute mask{.value = state.ctx->registers.w2}; memory::MemoryAttribute mask{.value = state.ctx->gpr.w2};
memory::MemoryAttribute value{.value = state.ctx->registers.w3}; memory::MemoryAttribute value{.value = state.ctx->gpr.w3};
auto maskedValue{mask.value | value.value}; auto maskedValue{mask.value | value.value};
if (maskedValue != mask.value || !mask.isUncached || mask.isDeviceShared || mask.isBorrowed || mask.isIpcLocked) { 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); state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask.value, value.value);
return; return;
} }
auto chunk{state.process->memory.Get(pointer)}; auto chunk{state.process->memory.Get(pointer)};
if (!chunk) { 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); state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", pointer);
return; return;
} }
if (!chunk->state.attributeChangeAllowed) { 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); state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", pointer);
return; return;
} }
@ -72,41 +72,41 @@ namespace skyline::kernel::svc {
state.process->memory.InsertChunk(newChunk); 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.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) { void MapMemory(const DeviceState &state) {
auto destination{reinterpret_cast<u8*>(state.ctx->registers.x0)}; auto destination{reinterpret_cast<u8*>(state.ctx->gpr.x0)};
auto source{reinterpret_cast<u8*>(state.ctx->registers.x1)}; auto source{reinterpret_cast<u8*>(state.ctx->gpr.x1)};
auto size{state.ctx->registers.x2}; auto size{state.ctx->gpr.x2};
if (!util::PageAligned(destination) || !util::PageAligned(source)) { 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); state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if (!util::PageAligned(size)) { 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); state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; return;
} }
auto stack{state.process->memory.stack}; auto stack{state.process->memory.stack};
if (!stack.IsInside(destination)) { 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); state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
auto chunk{state.process->memory.Get(source)}; auto chunk{state.process->memory.Get(source)};
if (!chunk) { 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); state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if (!chunk->state.mapAllowed) { 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); 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; return;
} }
@ -120,29 +120,29 @@ namespace skyline::kernel::svc {
object->item->UpdatePermission(source, size, {false, false, false}); 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.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) { void UnmapMemory(const DeviceState &state) {
auto source{reinterpret_cast<u8*>(state.ctx->registers.x0)}; auto source{reinterpret_cast<u8*>(state.ctx->gpr.x0)};
auto destination{reinterpret_cast<u8*>(state.ctx->registers.x1)}; auto destination{reinterpret_cast<u8*>(state.ctx->gpr.x1)};
auto size{state.ctx->registers.x2}; auto size{state.ctx->gpr.x2};
if (!util::PageAligned(destination) || !util::PageAligned(source)) { 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); state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if (!util::PageAligned(size)) { 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); state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; return;
} }
auto stack{state.process->memory.stack}; auto stack{state.process->memory.stack};
if (!stack.IsInside(source)) { 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); state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
@ -150,13 +150,13 @@ namespace skyline::kernel::svc {
auto sourceChunk{state.process->memory.Get(source)}; auto sourceChunk{state.process->memory.Get(source)};
auto destChunk{state.process->memory.Get(destination)}; auto destChunk{state.process->memory.Get(destination)};
if (!sourceChunk || !destChunk) { 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); state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if (!destChunk->state.mapAllowed) { 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); 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; return;
} }
@ -176,13 +176,13 @@ namespace skyline::kernel::svc {
state.process->CloseHandle(sourceObject->handle); 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.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{}; 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)}; auto chunk{state.process->memory.Get(pointer)};
if (chunk) { 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); 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.logger->Debug("svcExitProcess: Exiting process");
//state.os->KillThread(state.process->pid); //state.os->KillThread(state.process->pid);
} }
void CreateThread(DeviceState &state) { void CreateThread(const DeviceState &state) {
auto entry{reinterpret_cast<void*>(state.ctx->registers.x1)}; auto entry{reinterpret_cast<void*>(state.ctx->gpr.x1)};
auto entryArgument{state.ctx->registers.x2}; auto entryArgument{state.ctx->gpr.x2};
auto stackTop{reinterpret_cast<u8*>(state.ctx->registers.x3)}; auto stackTop{reinterpret_cast<u8*>(state.ctx->gpr.x3)};
auto priority{static_cast<i8>(state.ctx->registers.w4)}; auto priority{static_cast<i8>(state.ctx->gpr.w4)};
if (!constant::HosPriority.Valid(priority)) { if (!constant::HosPriority.Valid(priority)) {
state.ctx->registers.w0 = result::InvalidAddress; state.ctx->gpr.w0 = result::InvalidAddress;
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority); state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
return; return;
} }
@ -238,30 +238,30 @@ namespace skyline::kernel::svc {
auto thread{state.process->CreateThread(entry, entryArgument, priority, static_pointer_cast<type::KPrivateMemory>(stack->item))}; 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.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->gpr.w1 = thread->handle;
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
void StartThread(DeviceState &state) { void StartThread(const DeviceState &state) {
auto handle{state.ctx->registers.w0}; auto handle{state.ctx->gpr.w0};
try { try {
auto thread{state.process->GetHandle<type::KThread>(handle)}; auto thread{state.process->GetHandle<type::KThread>(handle)};
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->id); state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->id);
thread->Start(); thread->Start();
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) { } catch (const std::exception &) {
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle); 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.logger->Debug("svcExitThread: Exiting current thread: {}", state.thread->id);
state.os->KillThread(state.thread->id); state.os->KillThread(state.thread->id);
} }
void SleepThread(DeviceState &state) { void SleepThread(const DeviceState &state) {
auto in{state.ctx->registers.x0}; auto in{state.ctx->gpr.x0};
switch (in) { switch (in) {
case 0: case 0:
@ -272,69 +272,69 @@ namespace skyline::kernel::svc {
default: default:
state.logger->Debug("svcSleepThread: Thread sleeping for {} ns", in); state.logger->Debug("svcSleepThread: Thread sleeping for {} ns", in);
struct timespec spec{ struct timespec spec{
.tv_sec = static_cast<time_t>(state.ctx->registers.x0 / 1000000000), .tv_sec = static_cast<time_t>(state.ctx->gpr.x0 / 1000000000),
.tv_nsec = static_cast<long>(state.ctx->registers.x0 % 1000000000) .tv_nsec = static_cast<long>(state.ctx->gpr.x0 % 1000000000)
}; };
nanosleep(&spec, nullptr); nanosleep(&spec, nullptr);
} }
} }
void GetThreadPriority(DeviceState &state) { void GetThreadPriority(const DeviceState &state) {
auto handle{state.ctx->registers.w1}; auto handle{state.ctx->gpr.w1};
try { try {
auto priority{state.process->GetHandle<type::KThread>(handle)->priority}; auto priority{state.process->GetHandle<type::KThread>(handle)->priority};
state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority); state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority);
state.ctx->registers.w1 = priority; state.ctx->gpr.w1 = priority;
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) { } catch (const std::exception &) {
state.logger->Warn("svcGetThreadPriority: 'handle' invalid: 0x{:X}", handle); 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) { void SetThreadPriority(const DeviceState &state) {
auto handle{state.ctx->registers.w0}; auto handle{state.ctx->gpr.w0};
auto priority{state.ctx->registers.w1}; auto priority{state.ctx->gpr.w1};
try { try {
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority); state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority);
state.process->GetHandle<type::KThread>(handle)->UpdatePriority(static_cast<u8>(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 &) { } catch (const std::exception &) {
state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle); 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) { void ClearEvent(const DeviceState &state) {
auto object{state.process->GetHandle<type::KEvent>(state.ctx->registers.w0)}; auto object{state.process->GetHandle<type::KEvent>(state.ctx->gpr.w0)};
object->signalled = false; object->signalled = false;
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
void MapSharedMemory(DeviceState &state) { void MapSharedMemory(const DeviceState &state) {
try { try {
auto object{state.process->GetHandle<type::KSharedMemory>(state.ctx->registers.w0)}; auto object{state.process->GetHandle<type::KSharedMemory>(state.ctx->gpr.w0)};
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x1)}; auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x1)};
if (!util::PageAligned(pointer)) { 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); state.logger->Warn("svcMapSharedMemory: 'pointer' not page aligned: 0x{:X}", pointer);
return; return;
} }
auto size{state.ctx->registers.x2}; auto size{state.ctx->gpr.x2};
if (!util::PageAligned(size)) { 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); state.logger->Warn("svcMapSharedMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; 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)) { 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.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; return;
} }
@ -342,56 +342,56 @@ namespace skyline::kernel::svc {
object->Map(pointer, size, permission); object->Map(pointer, size, permission);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) { } catch (const std::exception &) {
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->registers.w0); state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->gpr.w0);
state.ctx->registers.w0 = result::InvalidHandle; state.ctx->gpr.w0 = result::InvalidHandle;
} }
} }
void CreateTransferMemory(DeviceState &state) { void CreateTransferMemory(const DeviceState &state) {
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x1)}; auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x1)};
if (!util::PageAligned(pointer)) { 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); state.logger->Warn("svcCreateTransferMemory: 'pointer' not page aligned: 0x{:X}", pointer);
return; return;
} }
auto size{state.ctx->registers.x2}; auto size{state.ctx->gpr.x2};
if (!util::PageAligned(size)) { 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); state.logger->Warn("svcCreateTransferMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; 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)) { 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.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; return;
} }
auto tmem{state.process->NewHandle<type::KTransferMemory>(pointer, size, permission)}; 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.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->gpr.w0 = Result{};
state.ctx->registers.w1 = tmem.handle; state.ctx->gpr.w1 = tmem.handle;
} }
void CloseHandle(DeviceState &state) { void CloseHandle(const DeviceState &state) {
auto handle{static_cast<KHandle>(state.ctx->registers.w0)}; auto handle{static_cast<KHandle>(state.ctx->gpr.w0)};
try { try {
state.process->CloseHandle(handle); state.process->CloseHandle(handle);
state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle); state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) { } catch (const std::exception &) {
state.logger->Warn("svcCloseHandle: 'handle' invalid: 0x{:X}", handle); 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) { void ResetSignal(const DeviceState &state) {
auto handle{state.ctx->registers.w0}; auto handle{state.ctx->gpr.w0};
try { try {
auto object{state.process->GetHandle(handle)}; auto object{state.process->GetHandle(handle)};
switch (object->objectType) { switch (object->objectType) {
@ -405,32 +405,32 @@ namespace skyline::kernel::svc {
default: { default: {
state.logger->Warn("svcResetSignal: 'handle' type invalid: 0x{:X} ({})", handle, object->objectType); 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; return;
} }
} }
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle); 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 &) { } catch (const std::out_of_range &) {
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle); state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = result::InvalidHandle; state.ctx->gpr.w0 = result::InvalidHandle;
return; 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 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) { if (numHandles > maxSyncHandles) {
state.ctx->registers.w0 = result::OutOfHandles; state.ctx->gpr.w0 = result::OutOfHandles;
return; return;
} }
std::string handleStr; std::string handleStr;
std::vector<std::shared_ptr<type::KSyncObject>> objectTable; 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) { for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle); handleStr += fmt::format("* 0x{:X}\n", handle);
@ -444,7 +444,7 @@ namespace skyline::kernel::svc {
break; break;
default: { default: {
state.ctx->registers.w0 = result::InvalidHandle; state.ctx->gpr.w0 = result::InvalidHandle;
return; return;
} }
} }
@ -452,14 +452,14 @@ namespace skyline::kernel::svc {
objectTable.push_back(std::static_pointer_cast<type::KSyncObject>(object)); 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); state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
auto start{util::GetTimeNs()}; auto start{util::GetTimeNs()};
while (true) { while (true) {
if (state.thread->cancelSync) { if (state.thread->cancelSync) {
state.thread->cancelSync = false; state.thread->cancelSync = false;
state.ctx->registers.w0 = result::Cancelled; state.ctx->gpr.w0 = result::Cancelled;
break; break;
} }
@ -467,8 +467,8 @@ namespace skyline::kernel::svc {
for (const auto &object : objectTable) { for (const auto &object : objectTable) {
if (object->signalled) { if (object->signalled) {
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]); state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
state.ctx->registers.w1 = index; state.ctx->gpr.w1 = index;
return; return;
} }
index++; index++;
@ -476,31 +476,31 @@ namespace skyline::kernel::svc {
if ((util::GetTimeNs() - start) >= timeout) { if ((util::GetTimeNs() - start) >= timeout) {
state.logger->Debug("svcWaitSynchronization: Wait has timed out"); state.logger->Debug("svcWaitSynchronization: Wait has timed out");
state.ctx->registers.w0 = result::TimedOut; state.ctx->gpr.w0 = result::TimedOut;
return; return;
} }
} }
} }
void CancelSynchronization(DeviceState &state) { void CancelSynchronization(const DeviceState &state) {
try { 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 &) { } catch (const std::exception &) {
state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->registers.w0); state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->gpr.w0);
state.ctx->registers.w0 = result::InvalidHandle; state.ctx->gpr.w0 = result::InvalidHandle;
} }
} }
void ArbitrateLock(DeviceState &state) { void ArbitrateLock(const DeviceState &state) {
auto pointer{reinterpret_cast<u32*>(state.ctx->registers.x1)}; auto pointer{reinterpret_cast<u32*>(state.ctx->gpr.x1)};
if (!util::WordAligned(pointer)) { if (!util::WordAligned(pointer)) {
state.logger->Warn("svcArbitrateLock: 'pointer' not word aligned: 0x{:X}", 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; return;
} }
auto ownerHandle{state.ctx->registers.w0}; auto ownerHandle{state.ctx->gpr.w0};
auto requesterHandle{state.ctx->registers.w2}; auto requesterHandle{state.ctx->gpr.w2};
if (requesterHandle != state.thread->handle) if (requesterHandle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", 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 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.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) { void ArbitrateUnlock(const DeviceState &state) {
auto mutex{reinterpret_cast<u32*>(state.ctx->registers.x0)}; auto mutex{reinterpret_cast<u32*>(state.ctx->gpr.x0)};
if (!util::WordAligned(mutex)) { if (!util::WordAligned(mutex)) {
state.logger->Warn("svcArbitrateUnlock: mutex pointer not word aligned: 0x{:X}", 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; return;
} }
@ -526,54 +526,54 @@ namespace skyline::kernel::svc {
if (state.process->MutexUnlock(mutex)) { if (state.process->MutexUnlock(mutex)) {
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", mutex); state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", mutex);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} else { } else {
state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", mutex); 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) { void WaitProcessWideKeyAtomic(const DeviceState &state) {
auto mutex{reinterpret_cast<u32*>(state.ctx->registers.x0)}; auto mutex{reinterpret_cast<u32*>(state.ctx->gpr.x0)};
if (!util::WordAligned(mutex)) { if (!util::WordAligned(mutex)) {
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex pointer not word aligned: 0x{:X}", 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; return;
} }
auto conditional{reinterpret_cast<void*>(state.ctx->registers.x1)}; auto conditional{reinterpret_cast<void*>(state.ctx->gpr.x1)};
auto handle{state.ctx->registers.w2}; auto handle{state.ctx->gpr.w2};
if (handle != state.thread->handle) if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", 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)) { if (!state.process->MutexUnlock(mutex)) {
state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", 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; 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); state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mutex, conditional, timeout);
if (state.process->ConditionalVariableWait(conditional, mutex, timeout)) { if (state.process->ConditionalVariableWait(conditional, mutex, timeout)) {
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex"); state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex");
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} else { } else {
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out"); state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
state.ctx->registers.w0 = result::TimedOut; state.ctx->gpr.w0 = result::TimedOut;
} }
} }
void SignalProcessWideKey(DeviceState &state) { void SignalProcessWideKey(const DeviceState &state) {
auto conditional{reinterpret_cast<void*>(state.ctx->registers.x0)}; auto conditional{reinterpret_cast<void*>(state.ctx->gpr.x0)};
auto count{state.ctx->registers.w1}; auto count{state.ctx->gpr.w1};
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", conditional, count); state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", conditional, count);
state.process->ConditionalVariableSignal(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; u64 tick;
asm("STR X1, [SP, #-16]!\n\t" asm("STR X1, [SP, #-16]!\n\t"
"MRS %0, CNTVCT_EL0\n\t" "MRS %0, CNTVCT_EL0\n\t"
@ -583,36 +583,36 @@ namespace skyline::kernel::svc {
"MRS X1, CNTFRQ_EL0\n\t" "MRS X1, CNTFRQ_EL0\n\t"
"UDIV %0, %0, X1\n\t" "UDIV %0, %0, X1\n\t"
"LDR X1, [SP], #16" : "=r"(tick)); "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 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{}; KHandle handle{};
if (port.compare("sm:") >= 0) { if (port.compare("sm:") >= 0) {
handle = state.process->NewHandle<type::KSession>(std::static_pointer_cast<service::BaseService>(state.os->serviceManager.smUserInterface)).handle; handle = state.process->NewHandle<type::KSession>(std::static_pointer_cast<service::BaseService>(state.os->serviceManager.smUserInterface)).handle;
} else { } else {
state.logger->Warn("svcConnectToNamedPort: Connecting to invalid port: '{}'", port); state.logger->Warn("svcConnectToNamedPort: Connecting to invalid port: '{}'", port);
state.ctx->registers.w0 = result::NotFound; state.ctx->gpr.w0 = result::NotFound;
return; return;
} }
state.logger->Debug("svcConnectToNamedPort: Connecting to port '{}' at 0x{:X}", port, handle); state.logger->Debug("svcConnectToNamedPort: Connecting to port '{}' at 0x{:X}", port, handle);
state.ctx->registers.w1 = handle; state.ctx->gpr.w1 = handle;
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
void SendSyncRequest(DeviceState &state) { void SendSyncRequest(const DeviceState &state) {
state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->registers.x0)); state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->gpr.x0));
state.ctx->registers.w0 = Result{}; 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 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{}; pid_t pid{};
if (handle != threadSelf) if (handle != threadSelf)
@ -622,24 +622,24 @@ namespace skyline::kernel::svc {
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid); state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
state.ctx->registers.x1 = static_cast<u64>(pid); state.ctx->gpr.x1 = static_cast<u64>(pid);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
void OutputDebugString(DeviceState &state) { void OutputDebugString(const DeviceState &state) {
auto debug{span(reinterpret_cast<u8*>(state.ctx->registers.x0), state.ctx->registers.x1).as_string()}; auto debug{span(reinterpret_cast<u8*>(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()};
if (debug.back() == '\n') if (debug.back() == '\n')
debug.remove_suffix(1); debug.remove_suffix(1);
state.logger->Info("Debug Output: {}", debug); state.logger->Info("Debug Output: {}", debug);
state.ctx->registers.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
void GetInfo(DeviceState &state) { void GetInfo(const DeviceState &state) {
auto id0{state.ctx->registers.w1}; auto id0{state.ctx->gpr.w1};
auto handle{state.ctx->registers.w2}; auto handle{state.ctx->gpr.w2};
auto id1{state.ctx->registers.x3}; auto id1{state.ctx->gpr.x3};
u64 out{}; u64 out{};
@ -715,13 +715,13 @@ namespace skyline::kernel::svc {
default: default:
state.logger->Warn("svcGetInfo: Unimplemented case ID0: {}, ID1: {}", id0, id1); state.logger->Warn("svcGetInfo: Unimplemented case ID0: {}, ID1: {}", id0, id1);
state.ctx->registers.w0 = result::InvalidEnumValue; state.ctx->gpr.w0 = result::InvalidEnumValue;
return; return;
} }
state.logger->Debug("svcGetInfo: ID0: {}, ID1: {}, Out: 0x{:X}", id0, id1, out); state.logger->Debug("svcGetInfo: ID0: {}, ID1: {}, Out: 0x{:X}", id0, id1, out);
state.ctx->registers.x1 = out; state.ctx->gpr.x1 = out;
state.ctx->registers.w0 = Result{}; 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 * @brief Sets the process heap to a given size, it can both extend and shrink the heap
* @url https://switchbrew.org/wiki/SVC#SetHeapSize * @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 * @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 * @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 * @brief Maps a memory range into a different range, mainly used for adding guard pages around stack
* @url https://switchbrew.org/wiki/SVC#SetMemoryAttribute * @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 * @brief Unmaps a region that was previously mapped with #MapMemory
* @url https://switchbrew.org/wiki/SVC#UnmapMemory * @url https://switchbrew.org/wiki/SVC#UnmapMemory
*/ */
void UnmapMemory(DeviceState &state); void UnmapMemory(const DeviceState &state);
/** /**
* @brief Query information about an address * @brief Query information about an address
* @url https://switchbrew.org/wiki/SVC#QueryMemory * @url https://switchbrew.org/wiki/SVC#QueryMemory
*/ */
void QueryMemory(DeviceState &state); void QueryMemory(const DeviceState &state);
/** /**
* @brief Exits the current process * @brief Exits the current process
* @url https://switchbrew.org/wiki/SVC#ExitProcess * @url https://switchbrew.org/wiki/SVC#ExitProcess
*/ */
void ExitProcess(DeviceState &state); void ExitProcess(const DeviceState &state);
/** /**
* @brief Create a thread in the current process * @brief Create a thread in the current process
* @url https://switchbrew.org/wiki/SVC#CreateThread * @url https://switchbrew.org/wiki/SVC#CreateThread
*/ */
void CreateThread(DeviceState &state); void CreateThread(const DeviceState &state);
/** /**
* @brief Starts the thread for the provided handle * @brief Starts the thread for the provided handle
* @url https://switchbrew.org/wiki/SVC#StartThread * @url https://switchbrew.org/wiki/SVC#StartThread
*/ */
void StartThread(DeviceState &state); void StartThread(const DeviceState &state);
/** /**
* @brief Exits the current thread * @brief Exits the current thread
* @url https://switchbrew.org/wiki/SVC#ExitThread * @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 * @brief Sleep for a specified amount of time, or yield thread
* @url https://switchbrew.org/wiki/SVC#SleepThread * @url https://switchbrew.org/wiki/SVC#SleepThread
*/ */
void SleepThread(DeviceState &state); void SleepThread(const DeviceState &state);
/** /**
* @brief Get priority of provided thread handle * @brief Get priority of provided thread handle
* @url https://switchbrew.org/wiki/SVC#GetThreadPriority * @url https://switchbrew.org/wiki/SVC#GetThreadPriority
*/ */
void GetThreadPriority(DeviceState &state); void GetThreadPriority(const DeviceState &state);
/** /**
* @brief Set priority of provided thread handle * @brief Set priority of provided thread handle
* @url https://switchbrew.org/wiki/SVC#SetThreadPriority * @url https://switchbrew.org/wiki/SVC#SetThreadPriority
*/ */
void SetThreadPriority(DeviceState &state); void SetThreadPriority(const DeviceState &state);
/** /**
* @brief Clears a KEvent of it's signal * @brief Clears a KEvent of it's signal
* @url https://switchbrew.org/wiki/SVC#ClearEvent * @url https://switchbrew.org/wiki/SVC#ClearEvent
*/ */
void ClearEvent(DeviceState &state); void ClearEvent(const DeviceState &state);
/** /**
* @brief Maps the block supplied by the handle * @brief Maps the block supplied by the handle
* @url https://switchbrew.org/wiki/SVC#MapSharedMemory * @url https://switchbrew.org/wiki/SVC#MapSharedMemory
*/ */
void MapSharedMemory(DeviceState &state); void MapSharedMemory(const DeviceState &state);
/** /**
* @brief Returns a handle to a KSharedMemory object * @brief Returns a handle to a KSharedMemory object
* @url https://switchbrew.org/wiki/SVC#CreateTransferMemory * @url https://switchbrew.org/wiki/SVC#CreateTransferMemory
*/ */
void CreateTransferMemory(DeviceState &state); void CreateTransferMemory(const DeviceState &state);
/** /**
* @brief Closes the specified handle * @brief Closes the specified handle
* @url https://switchbrew.org/wiki/SVC#CloseHandle * @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 * @brief Resets a particular KEvent or KProcess which is signalled
* @url https://switchbrew.org/wiki/SVC#ResetSignal * @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 * @brief Stalls a thread till a KSyncObject signals or the timeout has ended
* @url https://switchbrew.org/wiki/SVC#WaitSynchronization * @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 * @brief If the referenced thread is currently in a synchronization call, that call will be interrupted
* @url https://switchbrew.org/wiki/SVC#CancelSynchronization * @url https://switchbrew.org/wiki/SVC#CancelSynchronization
*/ */
void CancelSynchronization(DeviceState &state); void CancelSynchronization(const DeviceState &state);
/** /**
* @brief Locks a specified mutex * @brief Locks a specified mutex
* @url https://switchbrew.org/wiki/SVC#ArbitrateLock * @url https://switchbrew.org/wiki/SVC#ArbitrateLock
*/ */
void ArbitrateLock(DeviceState &state); void ArbitrateLock(const DeviceState &state);
/** /**
* @brief Unlocks a specified mutex * @brief Unlocks a specified mutex
* @url https://switchbrew.org/wiki/SVC#ArbitrateUnlock * @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) * @brief Waits on a process-wide key (Conditional-Variable)
* @url https://switchbrew.org/wiki/SVC#WaitProcessWideKeyAtomic * @url https://switchbrew.org/wiki/SVC#WaitProcessWideKeyAtomic
*/ */
void WaitProcessWideKeyAtomic(DeviceState &state); void WaitProcessWideKeyAtomic(const DeviceState &state);
/** /**
* @brief Signals a process-wide key (Conditional-Variable) * @brief Signals a process-wide key (Conditional-Variable)
* @url https://switchbrew.org/wiki/SVC#SignalProcessWideKey * @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 * @brief Returns the value of CNTPCT_EL0 on the Switch
* @url https://switchbrew.org/wiki/SVC#GetSystemTick * @url https://switchbrew.org/wiki/SVC#GetSystemTick
*/ */
void GetSystemTick(DeviceState &state); void GetSystemTick(const DeviceState &state);
/** /**
* @brief Connects to a named IPC port * @brief Connects to a named IPC port
* @url https://switchbrew.org/wiki/SVC#ConnectToNamedPort * @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 * @brief Send a synchronous IPC request to a service
* @url https://switchbrew.org/wiki/SVC#SendSyncRequest * @url https://switchbrew.org/wiki/SVC#SendSyncRequest
*/ */
void SendSyncRequest(DeviceState &state); void SendSyncRequest(const DeviceState &state);
/** /**
* @brief Retrieves the PID of a specific thread * @brief Retrieves the PID of a specific thread
* @url https://switchbrew.org/wiki/SVC#GetThreadId * @url https://switchbrew.org/wiki/SVC#GetThreadId
*/ */
void GetThreadId(DeviceState &state); void GetThreadId(const DeviceState &state);
/** /**
* @brief Outputs a debug string * @brief Outputs a debug string
* @url https://switchbrew.org/wiki/SVC#OutputDebugString * @url https://switchbrew.org/wiki/SVC#OutputDebugString
*/ */
void OutputDebugString(DeviceState &state); void OutputDebugString(const DeviceState &state);
/** /**
* @brief Retrieves a piece of information * @brief Retrieves a piece of information
* @url https://switchbrew.org/wiki/SVC#GetInfo * @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 * @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) nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01 SetHeapSize, // 0x01
nullptr, // 0x02 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); 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 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; memState = memory::states::CodeMutable;
state.process->memory.InsertChunk(ChunkDescriptor{ state.process->memory.InsertChunk(ChunkDescriptor{

View File

@ -32,7 +32,7 @@ namespace skyline::kernel::type {
void KProcess::InitializeHeap() { void KProcess::InitializeHeap() {
constexpr size_t DefaultHeapSize{0x200000}; 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() { u8 *KProcess::AllocateTlsSlot() {

View File

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

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <nce/guest.h>
#include "KSyncObject.h" #include "KSyncObject.h"
#include "KPrivateMemory.h" #include "KPrivateMemory.h"
#include "KSharedMemory.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 * @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: private:
KProcess *parent; KProcess *parent;
std::optional<std::thread> thread; //!< If this KThread is backed by a host thread then this'll hold it 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 size_t id; //!< Index of thread in parent process's KThread vector
std::shared_ptr<KPrivateMemory> stack; std::shared_ptr<KPrivateMemory> stack;
ThreadContext ctx{}; nce::ThreadContext ctx{};
void* entry; void* entry;
u64 entryArgument; u64 entryArgument;

View File

@ -16,82 +16,66 @@ extern bool Halt;
extern jobject Surface; extern jobject Surface;
extern skyline::GroupMutex JniMtx; extern skyline::GroupMutex JniMtx;
namespace skyline { namespace skyline::nce {
void NCE::KernelThread(pid_t thread) { void NCE::SvcHandler(u16 svc, ThreadContext *ctx) {
/* const auto &state{*ctx->state};
state.jvm->AttachThread();
try { try {
state.thread = state.process->threads.at(thread); auto function{kernel::svc::SvcTable[svc]};
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->kernel.ptr); if (function) {
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); state.logger->Debug("SVC called 0x{:X}", svc);
(*kernel::svc::SvcTable[svc])(state); (*function)(state);
} else { } else {
throw exception("Unimplemented SVC 0x{:X}", svc); throw exception("Unimplemented SVC 0x{:X}", svc);
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
throw exception("{} (SVC: 0x{:X})", e.what(), svc); throw exception("{} (SVC: 0x{:X})", e.what(), svc);
} // Jumps off the edge?
// Look into this
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;
}
}
} catch (const std::exception &e) {
state.logger->Error(e.what());
} catch (...) {
state.logger->Error("An unknown exception has occurred");
}
if (!Halt) {
if (thread == state.process->pid) {
JniMtx.lock(GroupMutex::Group::Group2);
state.os->KillThread(thread);
Halt = true;
JniMtx.unlock();
} else {
state.os->KillThread(thread);
} }
} }
state.jvm->DetachThread(); 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 (ctx.fault_address)
cpuContext += fmt::format("\nFault Address: 0x{:X}", ctx.fault_address);
if (ctx.sp)
cpuContext += fmt::format("\nStack Pointer: 0x{:X}", ctx.sp);
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.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(DeviceState &state) : state(state) {}
NCE::~NCE() {
for (auto &thread : threadMap)
thread.second->join();
}
void NCE::Execute() { void NCE::Execute() {
try { try {
while (true) { while (true) {
@ -113,265 +97,184 @@ namespace skyline {
} }
} }
inline ThreadContext *GetContext() { std::vector<u32> NCE::PatchCode(std::vector<u8> &code, u64 baseAddress, i64 patchBase) {
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) {
constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS
constexpr u32 TpidrroEl0{0x5E83}; // ID of TPIDRRO_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 CntfrqEl0{0x5F00}; // ID of CNTFRQ_EL0 in MRS
constexpr u32 CntpctEl0{0x5F01}; // ID of CNTPCT_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 CntvctEl0{0x5F02}; // ID of CNTVCT_EL0 in MRS
constexpr u32 TegraX1Freq{19200000}; // The clock frequency of the Tegra X1 (19.2 MHz) constexpr u32 TegraX1Freq{19200000}; // The clock frequency of the Tegra X1 (19.2 MHz)
constexpr size_t MainSvcTrampolineSize{17};
u32 *start{reinterpret_cast<u32 *>(code.data())}; size_t index{};
u32 *end{start + (code.size() / sizeof(u32))}; std::vector<u32> patch(guest::SaveCtxSize + guest::LoadCtxSize + MainSvcTrampolineSize);
i64 patchOffset{offset};
std::vector<u32> patch((guest::SaveCtxSize + guest::LoadCtxSize + guest::SvcHandlerSize) / sizeof(u32)); std::memcpy(patch.data(), reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize * sizeof(u32));
index += guest::SaveCtxSize;
std::memcpy(patch.data(), reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize); {
offset += guest::SaveCtxSize; /* Main SVC Trampoline */
std::memcpy(reinterpret_cast<u8 *>(patch.data()) + guest::SaveCtxSize, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize); /* Store LR in 16B of pre-allocated stack */
offset += guest::LoadCtxSize; patch[index++] = 0xF90007FE; // STR LR, [SP, #8]
std::memcpy(reinterpret_cast<u8 *>(patch.data()) + guest::SaveCtxSize + guest::LoadCtxSize, reinterpret_cast<void *>(&guest::SvcHandler), guest::SvcHandlerSize); /* Replace Skyline TLS with host TLS */
offset += guest::SvcHandlerSize; patch[index++] = 0xD53BD041; // MRS X1, TPIDR_EL0
patch[index++] = 0xF9417822; // LDR X2, [X1, #0x2F0] (ThreadContext::hostTpidrEl0)
static u64 frequency{}; /* Replace guest stack with host stack */
if (!frequency) 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)); asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
for (u32 *address{start}; address < end; address++) { i64 patchOffset{patchBase / i64(sizeof(u32))};
auto instrSvc{reinterpret_cast<instr::Svc *>(address)}; u32 *start{reinterpret_cast<u32 *>(code.data())};
auto instrMrs{reinterpret_cast<instr::Mrs *>(address)}; u32 *end{start + (code.size() / sizeof(u32))};
auto instrMsr{reinterpret_cast<instr::Msr *>(address)}; 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)};
if (instrSvc->Verify()) { if (svc.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 /* Per-SVC Trampoline */
instr::B bJunc(offset); patch.resize(patch.size() + 7);
constexpr u32 strLr{0xF81F0FFE}; // STR LR, [SP, #-16]! /* Rewrite SVC with B to trampoline */
offset += sizeof(strLr); *instruction = instr::B(patchOffset + index).raw;
instr::BL bSvCtx(patchOffset - offset); /* Save Context */
offset += sizeof(bSvCtx); patch[index++] = 0xF81F0FFE; // STR LR, [SP, #-16]!
patch[index] = instr::BL(-index).raw;
index++;
auto movPc{instr::MoveRegister<u64>(regs::X0, baseAddress + (address - start))}; /* Jump to main SVC trampoline */
offset += sizeof(u32) * movPc.size(); patch[index++] = instr::Movz(regs::W0, static_cast<u16>(svc.value)).raw;
patch[index] = instr::BL(guest::SaveCtxSize - index).raw;
index++;
instr::Movz movCmd(regs::W1, static_cast<u16>(instrSvc->value)); /* Restore Context and Return */
offset += sizeof(movCmd); 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));
instr::BL bSvcHandler((patchOffset + guest::SaveCtxSize + guest::LoadCtxSize) - offset); /* Rewrite MRS with B to trampoline */
offset += sizeof(bSvcHandler); *instruction = instr::B(patchOffset + index).raw;
instr::BL bLdCtx((patchOffset + guest::SaveCtxSize) - offset); /* Allocate Scratch Register */
offset += sizeof(bLdCtx); if (mrs.destReg != regs::X0)
patch[index++] = 0xF81F0FE0; // STR X0, [SP, #-16]!
constexpr u32 ldrLr{0xF84107FE}; // LDR LR, [SP], #16 /* Retrieve emulated TLS register from ThreadContext */
offset += sizeof(ldrLr); patch[index++] = 0xD53BD040; // MRS X0, TPIDR_EL0
if (mrs.srcReg == TpidrroEl0)
instr::B bret(-offset + sizeof(u32)); patch[index++] = 0xF9418000; // LDR X0, [X0, #0x300] (ThreadContext::tpidrroEl0)
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)
else else
ldrTls = 0xF9408400; // LDR X0, [X0, #264] (ThreadContext::tpidrEl0) patch[index++] = 0xF9418400; // LDR X0, [X0, #0x308] (ThreadContext::tpidrEl0)
offset += sizeof(ldrTls); /* Restore Scratch Register and Return */
if (mrs.destReg != regs::X0) {
u32 movXn{}; patch[index++] = instr::Mov(regs::X(mrs.destReg), regs::X0).raw;
u32 ldrX0{}; patch[index++] = 0xF84107E0; // LDR X0, [SP], #16
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);
} }
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
index++;
} else { } else {
// If the host clock frequency is the same as the Tegra X1's clock frequency if (frequency != TegraX1Freq) {
if (instrMrs->srcReg == CntpctEl0) { if (mrs.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 /* Physical Counter Load Emulation (With Rescaling) */
*address = instr::Mrs(CntvctEl0, regs::X(instrMrs->destReg)).raw; 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()) { } else if (msr.Verify()) {
if (instrMsr->destReg == TpidrEl0) { if (msr.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 /* Emulated TLS Register Store */
instr::B bJunc(offset); patch.resize(patch.size() + 6);
// Used to avoid conflicts as we cannot read the source register from the stack /* Rewrite MSR with B to trampoline */
bool x0x1{instrMrs->srcReg != regs::X0 && instrMrs->srcReg != regs::X1}; *instruction = instr::B(patchOffset + index).raw;
// Push two registers to stack that can be used to load the TLS and arguments into /* Allocate Scratch Registers */
u32 pushXn{x0x1 ? 0xA9BF07E0 : 0xA9BF0FE2}; // STP X(0/2), X(1/3), [SP, #-16]! bool x0x1{mrs.srcReg != regs::X0 && mrs.srcReg != regs::X1};
offset += sizeof(pushXn); 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 /* Store new TLS value into ThreadContext */
offset += sizeof(loadRealTls); 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)); /* Restore Scratch Registers and Return */
offset += sizeof(moveParam); patch[index++] = x0x1 ? 0xA8C107E0 : 0xA8C10FE2; // LDP X(0/2), X(1/3), [SP], #16
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
u32 storeEmuTls{x0x1 ? 0xF9008401 : 0xF9008403}; // STR X(1/3), [X0, #264] (ThreadContext::tpidrEl0) index++;
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);
} }
} }
patchOffset--;
offset -= sizeof(u32);
patchOffset -= sizeof(u32);
} }
return patch; return patch;
} }

View File

@ -6,47 +6,28 @@
#include "common.h" #include "common.h"
#include <sys/wait.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 * @brief The NCE (Native Code Execution) class is responsible for managing the state of catching instructions and directly controlling processes/threads
*/ */
class NCE { class NCE {
private: private:
DeviceState &state; 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
/** static void SvcHandler(u16 svc, ThreadContext* ctx);
* @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);
public: public:
NCE(DeviceState &state); static void SignalHandler(int signal, siginfo *info, void *context);
~NCE(); NCE(DeviceState &state);
void Execute(); void Execute();
void SignalHandler(int signal, const siginfo &info, const ucontext &ucontext);
/** /**
* @brief A delegator to the real signal handler after restoring context * @brief Generates a patch section for the supplied code
*/
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
* @param baseAddress The address at which the code is mapped * @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 .text
.global SaveCtx .global SaveCtx
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 MRS LR, TPIDR_EL0
STP X0, X1, [LR, #16]
STP X2, X3, [LR, #32] /* Store GP Registers */
STP X4, X5, [LR, #48] STP X0, X1, [LR, #0]
STP X6, X7, [LR, #64] STP X2, X3, [LR, #16]
STP X8, X9, [LR, #80] STP X4, X5, [LR, #32]
STP X10, X11, [LR, #96] STP X6, X7, [LR, #48]
STP X12, X13, [LR, #112] STP X8, X9, [LR, #64]
STP X14, X15, [LR, #128] STP X10, X11, [LR, #80]
STP X16, X17, [LR, #144] STP X12, X13, [LR, #96]
STP X18, X19, [LR, #160] STP X14, X15, [LR, #112]
STP X20, X21, [LR, #176] STP X16, X17, [LR, #128]
STP X22, X23, [LR, #192] STP X18, X19, [LR, #144]
STP X24, X25, [LR, #208] STP X20, X21, [LR, #160]
STP X26, X27, [LR, #224] STP X22, X23, [LR, #176]
STP X28, X29, [LR, #240] STP X24, X25, [LR, #192]
LDR LR, [SP], #16 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 RET
.global LoadCtx .global LoadCtx
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 MRS LR, TPIDR_EL0
LDP X0, X1, [LR, #16]
LDP X2, X3, [LR, #32] /* Load FP Registers */
LDP X4, X5, [LR, #48] LDP Q0, Q1, [LR, #240]
LDP X6, X7, [LR, #64] LDP Q2, Q3, [LR, #272]
LDP X8, X9, [LR, #80] LDP Q4, Q5, [LR, #304]
LDP X10, X11, [LR, #96] LDP Q6, Q7, [LR, #336]
LDP X12, X13, [LR, #112] LDP Q8, Q9, [LR, #368]
LDP X14, X15, [LR, #128] LDP Q10, Q11, [LR, #400]
LDP X16, X17, [LR, #144] LDP Q12, Q13, [LR, #432]
LDP X18, X19, [LR, #160] LDP Q14, Q15, [LR, #464]
LDP X20, X21, [LR, #176] LDP Q16, Q17, [LR, #496]
LDP X22, X23, [LR, #192] LDP Q18, Q19, [LR, #528]
LDP X24, X25, [LR, #208] LDP Q20, Q21, [LR, #560]
LDP X26, X27, [LR, #224] LDP Q22, Q23, [LR, #592]
LDP X28, X29, [LR, #240] LDP Q24, Q25, [LR, #624]
LDR LR, [SP], #16 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 RET
.global RescaleClock .global RescaleClock

View File

@ -3,39 +3,160 @@
#pragma once #pragma once
#include "guest_common.h" #include <common.h>
namespace skyline { namespace skyline {
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 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
*/
union FpRegisters {
std::array<u128, 32> regs;
u32 fpsr;
u32 fpcr;
};
/**
* @brief A per-thread context for guest threads
* @note It's stored in TPIDR_EL0 while running the guest
*/
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;
};
namespace guest { namespace guest {
constexpr size_t SaveCtxSize{20 * sizeof(u32)}; //!< The size of the SaveCtx function in 32-bit ARMv8 instructions constexpr size_t SaveCtxSize{39}; //!< 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 LoadCtxSize{39}; //!< 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 constexpr size_t RescaleClockSize{16}; //!< 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
/** /**
* @brief Saves the context from CPU registers into TLS * @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); extern "C" void SaveCtx(void);
/** /**
* @brief Loads the context from TLS into CPU registers * @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); extern "C" void LoadCtx(void);
/** /**
* @brief Rescales the clock to Tegra X1 levels and puts the output on stack * @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); extern "C" __noreturn void RescaleClock(void);
}
/**
* @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);
} }
} }

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> #include <common.h>
namespace skyline { namespace skyline::nce {
namespace regs { 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 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 }; 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 { struct B {
public: 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) { constexpr B(i32 offset) {
this->offset = static_cast<i32>(offset / sizeof(u32)); this->offset = offset;
sig = 0x5; sig = 0x5;
} }
@ -146,10 +146,10 @@ namespace skyline {
struct BL { struct BL {
public: 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) { constexpr BL(i32 offset) {
this->offset = static_cast<i32>(offset / sizeof(u32)); this->offset = offset;
sig = 0x25; sig = 0x25;
} }
@ -288,20 +288,26 @@ namespace skyline {
* @param destination The destination register of the operation * @param destination The destination register of the operation
* @param value The value to insert into the register * @param value The value to insert into the register
* @return A array with the instructions to insert the value * @return A array with the instructions to insert the value
* @note 0 is returned for any instruction that isn't required
*/ */
template<typename Type> template<typename Type>
constexpr std::array<u32, sizeof(Type) / sizeof(u16)> MoveRegister(regs::X destination, Type value) { constexpr std::array<u32, sizeof(Type) / sizeof(u16)> MoveRegister(regs::X destination, Type value) {
std::array<u32, sizeof(Type) / sizeof(u16)> instructions; std::array<u32, sizeof(Type) / sizeof(u16)> instructions;
auto valuePointer{reinterpret_cast<u16 *>(&value)}; auto valuePointer{reinterpret_cast<u16 *>(&value)};
bool zeroed{};
u8 offset{}; u8 offset{};
for (auto &instruction : instructions) { for (auto &instruction : instructions) {
if (offset) auto offsetValue{*(valuePointer + offset)};
instruction = instr::Movk(destination, *(valuePointer + offset), offset).raw; if (offsetValue) {
else if (zeroed) {
instruction = instr::Movz(destination, *(valuePointer + offset), offset).raw; instruction = instr::Movk(destination, offsetValue, offset).raw;
} else {
instruction = instr::Movz(destination, offsetValue, offset).raw;
zeroed = true;
}
}
offset++; offset++;
} }