mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 09:31:52 +01:00
NCE3: In-Process Guest Execution
This commit is contained in:
parent
90127740f0
commit
1db76dee1e
@ -34,7 +34,6 @@ add_library(skyline SHARED
|
||||
${source_DIR}/loader_jni.cpp
|
||||
${source_DIR}/skyline/common.cpp
|
||||
${source_DIR}/skyline/nce/guest.S
|
||||
${source_DIR}/skyline/nce/guest.cpp
|
||||
${source_DIR}/skyline/nce.cpp
|
||||
${source_DIR}/skyline/jvm.cpp
|
||||
${source_DIR}/skyline/audio.cpp
|
||||
|
@ -157,12 +157,12 @@ namespace skyline {
|
||||
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &process, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)
|
||||
: os(os), jvm(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)), process(process) {
|
||||
// We assign these later as they use the state in their constructor and we don't want null pointers
|
||||
nce = std::make_shared<NCE>(*this);
|
||||
nce = std::make_shared<nce::NCE>(*this);
|
||||
gpu = std::make_shared<gpu::GPU>(*this);
|
||||
audio = std::make_shared<audio::Audio>(*this);
|
||||
input = std::make_shared<input::Input>(*this);
|
||||
}
|
||||
|
||||
thread_local std::shared_ptr<kernel::type::KThread> DeviceState::thread = nullptr;
|
||||
thread_local ThreadContext *DeviceState::ctx = nullptr;
|
||||
thread_local nce::ThreadContext *DeviceState::ctx = nullptr;
|
||||
}
|
||||
|
@ -23,11 +23,23 @@
|
||||
#include <frozen/unordered_map.h>
|
||||
#include <frozen/string.h>
|
||||
#include <jni.h>
|
||||
#include "nce/guest_common.h"
|
||||
|
||||
#define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage)
|
||||
|
||||
namespace skyline {
|
||||
namespace frz = frozen;
|
||||
using u128 = __uint128_t; //!< Unsigned 128-bit integer
|
||||
using u64 = __uint64_t; //!< Unsigned 64-bit integer
|
||||
using u32 = __uint32_t; //!< Unsigned 32-bit integer
|
||||
using u16 = __uint16_t; //!< Unsigned 16-bit integer
|
||||
using u8 = __uint8_t; //!< Unsigned 8-bit integer
|
||||
using i128 = __int128_t; //!< Signed 128-bit integer
|
||||
using i64 = __int64_t; //!< Signed 64-bit integer
|
||||
using i32 = __int32_t; //!< Signed 32-bit integer
|
||||
using i16 = __int16_t; //!< Signed 16-bit integer
|
||||
using i8 = __int8_t; //!< Signed 8-bit integer
|
||||
|
||||
using KHandle = u32; //!< The type of a kernel handle
|
||||
namespace frz = frozen;
|
||||
|
||||
/**
|
||||
* @brief The result of an operation in HOS
|
||||
@ -105,9 +117,8 @@ namespace skyline {
|
||||
* @return The current time in nanoseconds
|
||||
*/
|
||||
inline u64 GetTimeNs() {
|
||||
static u64 frequency{};
|
||||
if (!frequency)
|
||||
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
|
||||
u64 frequency;
|
||||
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
|
||||
u64 ticks;
|
||||
asm("MRS %0, CNTVCT_EL0" : "=r"(ticks));
|
||||
return ((ticks / frequency) * constant::NsInSecond) + (((ticks % frequency) * constant::NsInSecond + (frequency / 2)) / frequency);
|
||||
@ -529,6 +540,10 @@ namespace skyline {
|
||||
void List(const std::shared_ptr<Logger> &logger);
|
||||
};
|
||||
|
||||
namespace nce {
|
||||
class NCE;
|
||||
struct ThreadContext;
|
||||
}
|
||||
class JvmManager;
|
||||
namespace gpu {
|
||||
class GPU;
|
||||
@ -559,8 +574,8 @@ namespace skyline {
|
||||
kernel::OS *os;
|
||||
std::shared_ptr<kernel::type::KProcess> &process;
|
||||
thread_local static std::shared_ptr<kernel::type::KThread> thread; //!< The KThread of the thread which accesses this object
|
||||
thread_local static ThreadContext *ctx; //!< The context of the guest thread for the corresponding host thread
|
||||
std::shared_ptr<NCE> nce;
|
||||
thread_local static nce::ThreadContext *ctx; //!< The context of the guest thread for the corresponding host thread
|
||||
std::shared_ptr<nce::NCE> nce;
|
||||
std::shared_ptr<gpu::GPU> gpu;
|
||||
std::shared_ptr<audio::Audio> audio;
|
||||
std::shared_ptr<input::Input> input;
|
||||
|
@ -127,7 +127,8 @@ namespace skyline::kernel {
|
||||
size_t MemoryManager::GetProgramSize() {
|
||||
size_t size{};
|
||||
for (const auto &chunk : chunks)
|
||||
size += chunk.size;
|
||||
if (chunk.state != memory::states::Unmapped)
|
||||
size += chunk.size;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,14 @@ namespace skyline {
|
||||
|
||||
constexpr MemoryState() : value(0) {}
|
||||
|
||||
constexpr bool operator==(const MemoryState& other) const {
|
||||
return value == other.value;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const MemoryState& other) const {
|
||||
return value != other.value;
|
||||
}
|
||||
|
||||
struct {
|
||||
MemoryType type;
|
||||
bool permissionChangeAllowed : 1; //!< If the application can use svcSetMemoryPermission on this block
|
||||
|
@ -7,12 +7,12 @@
|
||||
#include "svc.h"
|
||||
|
||||
namespace skyline::kernel::svc {
|
||||
void SetHeapSize(DeviceState &state) {
|
||||
auto size{state.ctx->registers.w1};
|
||||
void SetHeapSize(const DeviceState &state) {
|
||||
auto size{state.ctx->gpr.w1};
|
||||
|
||||
if (!util::IsAligned(size, 0x200000)) {
|
||||
state.ctx->registers.w0 = result::InvalidSize;
|
||||
state.ctx->registers.x1 = 0;
|
||||
state.ctx->gpr.w0 = result::InvalidSize;
|
||||
state.ctx->gpr.x1 = 0;
|
||||
|
||||
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
|
||||
return;
|
||||
@ -21,46 +21,46 @@ namespace skyline::kernel::svc {
|
||||
auto &heap{state.process->heap};
|
||||
heap->Resize(size);
|
||||
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->registers.x1 = reinterpret_cast<u64>(heap->ptr);
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
state.ctx->gpr.x1 = reinterpret_cast<u64>(heap->ptr);
|
||||
|
||||
state.logger->Debug("svcSetHeapSize: Allocated at 0x{:X} for 0x{:X} bytes", heap->ptr, heap->size);
|
||||
state.logger->Debug("svcSetHeapSize: Allocated at {} for 0x{:X} bytes", heap->ptr, heap->size);
|
||||
}
|
||||
|
||||
void SetMemoryAttribute(DeviceState &state) {
|
||||
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x0)};
|
||||
void SetMemoryAttribute(const DeviceState &state) {
|
||||
auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x0)};
|
||||
if (!util::PageAligned(pointer)) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcSetMemoryAttribute: 'pointer' not page aligned: 0x{:X}", pointer);
|
||||
return;
|
||||
}
|
||||
|
||||
auto size{state.ctx->registers.x1};
|
||||
auto size{state.ctx->gpr.x1};
|
||||
if (!util::PageAligned(size)) {
|
||||
state.ctx->registers.w0 = result::InvalidSize;
|
||||
state.ctx->gpr.w0 = result::InvalidSize;
|
||||
state.logger->Warn("svcSetMemoryAttribute: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
|
||||
memory::MemoryAttribute mask{.value = state.ctx->registers.w2};
|
||||
memory::MemoryAttribute value{.value = state.ctx->registers.w3};
|
||||
memory::MemoryAttribute mask{.value = state.ctx->gpr.w2};
|
||||
memory::MemoryAttribute value{.value = state.ctx->gpr.w3};
|
||||
|
||||
auto maskedValue{mask.value | value.value};
|
||||
if (maskedValue != mask.value || !mask.isUncached || mask.isDeviceShared || mask.isBorrowed || mask.isIpcLocked) {
|
||||
state.ctx->registers.w0 = result::InvalidCombination;
|
||||
state.ctx->gpr.w0 = result::InvalidCombination;
|
||||
state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask.value, value.value);
|
||||
return;
|
||||
}
|
||||
|
||||
auto chunk{state.process->memory.Get(pointer)};
|
||||
if (!chunk) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", pointer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chunk->state.attributeChangeAllowed) {
|
||||
state.ctx->registers.w0 = result::InvalidState;
|
||||
state.ctx->gpr.w0 = result::InvalidState;
|
||||
state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", pointer);
|
||||
return;
|
||||
}
|
||||
@ -72,41 +72,41 @@ namespace skyline::kernel::svc {
|
||||
state.process->memory.InsertChunk(newChunk);
|
||||
|
||||
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} for 0x{:X} bytes", bool(value.isUncached), pointer, size);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void MapMemory(DeviceState &state) {
|
||||
auto destination{reinterpret_cast<u8*>(state.ctx->registers.x0)};
|
||||
auto source{reinterpret_cast<u8*>(state.ctx->registers.x1)};
|
||||
auto size{state.ctx->registers.x2};
|
||||
void MapMemory(const DeviceState &state) {
|
||||
auto destination{reinterpret_cast<u8*>(state.ctx->gpr.x0)};
|
||||
auto source{reinterpret_cast<u8*>(state.ctx->gpr.x1)};
|
||||
auto size{state.ctx->gpr.x2};
|
||||
|
||||
if (!util::PageAligned(destination) || !util::PageAligned(source)) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!util::PageAligned(size)) {
|
||||
state.ctx->registers.w0 = result::InvalidSize;
|
||||
state.ctx->gpr.w0 = result::InvalidSize;
|
||||
state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto stack{state.process->memory.stack};
|
||||
if (!stack.IsInside(destination)) {
|
||||
state.ctx->registers.w0 = result::InvalidMemoryRegion;
|
||||
state.ctx->gpr.w0 = result::InvalidMemoryRegion;
|
||||
state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto chunk{state.process->memory.Get(source)};
|
||||
if (!chunk) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
if (!chunk->state.mapAllowed) {
|
||||
state.ctx->registers.w0 = result::InvalidState;
|
||||
state.ctx->gpr.w0 = result::InvalidState;
|
||||
state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X}, Size: 0x{:X}, MemoryState: 0x{:X}", source, destination, size, chunk->state.value);
|
||||
return;
|
||||
}
|
||||
@ -120,29 +120,29 @@ namespace skyline::kernel::svc {
|
||||
object->item->UpdatePermission(source, size, {false, false, false});
|
||||
|
||||
state.logger->Debug("svcMapMemory: Mapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, fmt::ptr(source + size), destination, fmt::ptr(destination + size), size);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void UnmapMemory(DeviceState &state) {
|
||||
auto source{reinterpret_cast<u8*>(state.ctx->registers.x0)};
|
||||
auto destination{reinterpret_cast<u8*>(state.ctx->registers.x1)};
|
||||
auto size{state.ctx->registers.x2};
|
||||
void UnmapMemory(const DeviceState &state) {
|
||||
auto source{reinterpret_cast<u8*>(state.ctx->gpr.x0)};
|
||||
auto destination{reinterpret_cast<u8*>(state.ctx->gpr.x1)};
|
||||
auto size{state.ctx->gpr.x2};
|
||||
|
||||
if (!util::PageAligned(destination) || !util::PageAligned(source)) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!util::PageAligned(size)) {
|
||||
state.ctx->registers.w0 = result::InvalidSize;
|
||||
state.ctx->gpr.w0 = result::InvalidSize;
|
||||
state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto stack{state.process->memory.stack};
|
||||
if (!stack.IsInside(source)) {
|
||||
state.ctx->registers.w0 = result::InvalidMemoryRegion;
|
||||
state.ctx->gpr.w0 = result::InvalidMemoryRegion;
|
||||
state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
@ -150,13 +150,13 @@ namespace skyline::kernel::svc {
|
||||
auto sourceChunk{state.process->memory.Get(source)};
|
||||
auto destChunk{state.process->memory.Get(destination)};
|
||||
if (!sourceChunk || !destChunk) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!destChunk->state.mapAllowed) {
|
||||
state.ctx->registers.w0 = result::InvalidState;
|
||||
state.ctx->gpr.w0 = result::InvalidState;
|
||||
state.logger->Warn("svcUnmapMemory: Destination doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, destChunk->state.value);
|
||||
return;
|
||||
}
|
||||
@ -176,13 +176,13 @@ namespace skyline::kernel::svc {
|
||||
state.process->CloseHandle(sourceObject->handle);
|
||||
|
||||
state.logger->Debug("svcUnmapMemory: Unmapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void QueryMemory(DeviceState &state) {
|
||||
void QueryMemory(const DeviceState &state) {
|
||||
memory::MemoryInfo memInfo{};
|
||||
|
||||
auto pointer{reinterpret_cast<u8*>(state.ctx->registers.x2)};
|
||||
auto pointer{reinterpret_cast<u8*>(state.ctx->gpr.x2)};
|
||||
auto chunk{state.process->memory.Get(pointer)};
|
||||
|
||||
if (chunk) {
|
||||
@ -209,24 +209,24 @@ namespace skyline::kernel::svc {
|
||||
state.logger->Debug("svcQueryMemory: Trying to query memory outside of the application's address space: 0x{:X}", pointer);
|
||||
}
|
||||
|
||||
*reinterpret_cast<memory::MemoryInfo*>(state.ctx->registers.x0) = memInfo;
|
||||
*reinterpret_cast<memory::MemoryInfo*>(state.ctx->gpr.x0) = memInfo;
|
||||
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void ExitProcess(DeviceState &state) {
|
||||
void ExitProcess(const DeviceState &state) {
|
||||
state.logger->Debug("svcExitProcess: Exiting process");
|
||||
//state.os->KillThread(state.process->pid);
|
||||
}
|
||||
|
||||
void CreateThread(DeviceState &state) {
|
||||
auto entry{reinterpret_cast<void*>(state.ctx->registers.x1)};
|
||||
auto entryArgument{state.ctx->registers.x2};
|
||||
auto stackTop{reinterpret_cast<u8*>(state.ctx->registers.x3)};
|
||||
auto priority{static_cast<i8>(state.ctx->registers.w4)};
|
||||
void CreateThread(const DeviceState &state) {
|
||||
auto entry{reinterpret_cast<void*>(state.ctx->gpr.x1)};
|
||||
auto entryArgument{state.ctx->gpr.x2};
|
||||
auto stackTop{reinterpret_cast<u8*>(state.ctx->gpr.x3)};
|
||||
auto priority{static_cast<i8>(state.ctx->gpr.w4)};
|
||||
|
||||
if (!constant::HosPriority.Valid(priority)) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
|
||||
return;
|
||||
}
|
||||
@ -238,30 +238,30 @@ namespace skyline::kernel::svc {
|
||||
auto thread{state.process->CreateThread(entry, entryArgument, priority, static_pointer_cast<type::KPrivateMemory>(stack->item))};
|
||||
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, ID: {})", thread->handle, entry, entryArgument, stackTop, priority, thread->id);
|
||||
|
||||
state.ctx->registers.w1 = thread->handle;
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w1 = thread->handle;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void StartThread(DeviceState &state) {
|
||||
auto handle{state.ctx->registers.w0};
|
||||
void StartThread(const DeviceState &state) {
|
||||
auto handle{state.ctx->gpr.w0};
|
||||
try {
|
||||
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
||||
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->id);
|
||||
thread->Start();
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void ExitThread(DeviceState &state) {
|
||||
void ExitThread(const DeviceState &state) {
|
||||
state.logger->Debug("svcExitThread: Exiting current thread: {}", state.thread->id);
|
||||
state.os->KillThread(state.thread->id);
|
||||
}
|
||||
|
||||
void SleepThread(DeviceState &state) {
|
||||
auto in{state.ctx->registers.x0};
|
||||
void SleepThread(const DeviceState &state) {
|
||||
auto in{state.ctx->gpr.x0};
|
||||
|
||||
switch (in) {
|
||||
case 0:
|
||||
@ -272,69 +272,69 @@ namespace skyline::kernel::svc {
|
||||
default:
|
||||
state.logger->Debug("svcSleepThread: Thread sleeping for {} ns", in);
|
||||
struct timespec spec{
|
||||
.tv_sec = static_cast<time_t>(state.ctx->registers.x0 / 1000000000),
|
||||
.tv_nsec = static_cast<long>(state.ctx->registers.x0 % 1000000000)
|
||||
.tv_sec = static_cast<time_t>(state.ctx->gpr.x0 / 1000000000),
|
||||
.tv_nsec = static_cast<long>(state.ctx->gpr.x0 % 1000000000)
|
||||
};
|
||||
nanosleep(&spec, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void GetThreadPriority(DeviceState &state) {
|
||||
auto handle{state.ctx->registers.w1};
|
||||
void GetThreadPriority(const DeviceState &state) {
|
||||
auto handle{state.ctx->gpr.w1};
|
||||
try {
|
||||
auto priority{state.process->GetHandle<type::KThread>(handle)->priority};
|
||||
state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority);
|
||||
|
||||
state.ctx->registers.w1 = priority;
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w1 = priority;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcGetThreadPriority: 'handle' invalid: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void SetThreadPriority(DeviceState &state) {
|
||||
auto handle{state.ctx->registers.w0};
|
||||
auto priority{state.ctx->registers.w1};
|
||||
void SetThreadPriority(const DeviceState &state) {
|
||||
auto handle{state.ctx->gpr.w0};
|
||||
auto priority{state.ctx->gpr.w1};
|
||||
|
||||
try {
|
||||
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority);
|
||||
state.process->GetHandle<type::KThread>(handle)->UpdatePriority(static_cast<u8>(priority));
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearEvent(DeviceState &state) {
|
||||
auto object{state.process->GetHandle<type::KEvent>(state.ctx->registers.w0)};
|
||||
void ClearEvent(const DeviceState &state) {
|
||||
auto object{state.process->GetHandle<type::KEvent>(state.ctx->gpr.w0)};
|
||||
object->signalled = false;
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void MapSharedMemory(DeviceState &state) {
|
||||
void MapSharedMemory(const DeviceState &state) {
|
||||
try {
|
||||
auto object{state.process->GetHandle<type::KSharedMemory>(state.ctx->registers.w0)};
|
||||
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x1)};
|
||||
auto object{state.process->GetHandle<type::KSharedMemory>(state.ctx->gpr.w0)};
|
||||
auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x1)};
|
||||
|
||||
if (!util::PageAligned(pointer)) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcMapSharedMemory: 'pointer' not page aligned: 0x{:X}", pointer);
|
||||
return;
|
||||
}
|
||||
|
||||
auto size{state.ctx->registers.x2};
|
||||
auto size{state.ctx->gpr.x2};
|
||||
if (!util::PageAligned(size)) {
|
||||
state.ctx->registers.w0 = result::InvalidSize;
|
||||
state.ctx->gpr.w0 = result::InvalidSize;
|
||||
state.logger->Warn("svcMapSharedMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->registers.w3)};
|
||||
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->gpr.w3)};
|
||||
if ((permission.w && !permission.r) || (permission.x && !permission.r)) {
|
||||
state.logger->Warn("svcMapSharedMemory: 'permission' invalid: {}{}{}", permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');
|
||||
state.ctx->registers.w0 = result::InvalidNewMemoryPermission;
|
||||
state.ctx->gpr.w0 = result::InvalidNewMemoryPermission;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -342,56 +342,56 @@ namespace skyline::kernel::svc {
|
||||
|
||||
object->Map(pointer, size, permission);
|
||||
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->gpr.w0);
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateTransferMemory(DeviceState &state) {
|
||||
auto pointer{reinterpret_cast<u8 *>(state.ctx->registers.x1)};
|
||||
void CreateTransferMemory(const DeviceState &state) {
|
||||
auto pointer{reinterpret_cast<u8 *>(state.ctx->gpr.x1)};
|
||||
if (!util::PageAligned(pointer)) {
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
state.logger->Warn("svcCreateTransferMemory: 'pointer' not page aligned: 0x{:X}", pointer);
|
||||
return;
|
||||
}
|
||||
|
||||
auto size{state.ctx->registers.x2};
|
||||
auto size{state.ctx->gpr.x2};
|
||||
if (!util::PageAligned(size)) {
|
||||
state.ctx->registers.w0 = result::InvalidSize;
|
||||
state.ctx->gpr.w0 = result::InvalidSize;
|
||||
state.logger->Warn("svcCreateTransferMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->registers.w3)};
|
||||
auto permission{*reinterpret_cast<memory::Permission *>(&state.ctx->gpr.w3)};
|
||||
if ((permission.w && !permission.r) || (permission.x && !permission.r)) {
|
||||
state.logger->Warn("svcCreateTransferMemory: 'permission' invalid: {}{}{}", permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');
|
||||
state.ctx->registers.w0 = result::InvalidNewMemoryPermission;
|
||||
state.ctx->gpr.w0 = result::InvalidNewMemoryPermission;
|
||||
return;
|
||||
}
|
||||
|
||||
auto tmem{state.process->NewHandle<type::KTransferMemory>(pointer, size, permission)};
|
||||
state.logger->Debug("svcCreateTransferMemory: Creating transfer memory at 0x{:X} for {} bytes ({}{}{})", pointer, size, permission.r ? 'R' : '-', permission.w ? 'W' : '-', permission.x ? 'X' : '-');
|
||||
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->registers.w1 = tmem.handle;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
state.ctx->gpr.w1 = tmem.handle;
|
||||
}
|
||||
|
||||
void CloseHandle(DeviceState &state) {
|
||||
auto handle{static_cast<KHandle>(state.ctx->registers.w0)};
|
||||
void CloseHandle(const DeviceState &state) {
|
||||
auto handle{static_cast<KHandle>(state.ctx->gpr.w0)};
|
||||
try {
|
||||
state.process->CloseHandle(handle);
|
||||
state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcCloseHandle: 'handle' invalid: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetSignal(DeviceState &state) {
|
||||
auto handle{state.ctx->registers.w0};
|
||||
void ResetSignal(const DeviceState &state) {
|
||||
auto handle{state.ctx->gpr.w0};
|
||||
try {
|
||||
auto object{state.process->GetHandle(handle)};
|
||||
switch (object->objectType) {
|
||||
@ -405,32 +405,32 @@ namespace skyline::kernel::svc {
|
||||
|
||||
default: {
|
||||
state.logger->Warn("svcResetSignal: 'handle' type invalid: 0x{:X} ({})", handle, object->objectType);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitSynchronization(DeviceState &state) {
|
||||
void WaitSynchronization(const DeviceState &state) {
|
||||
constexpr u8 maxSyncHandles{0x40}; // The total amount of handles that can be passed to WaitSynchronization
|
||||
|
||||
auto numHandles{state.ctx->registers.w2};
|
||||
auto numHandles{state.ctx->gpr.w2};
|
||||
if (numHandles > maxSyncHandles) {
|
||||
state.ctx->registers.w0 = result::OutOfHandles;
|
||||
state.ctx->gpr.w0 = result::OutOfHandles;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string handleStr;
|
||||
std::vector<std::shared_ptr<type::KSyncObject>> objectTable;
|
||||
span waitHandles(reinterpret_cast<KHandle*>(state.ctx->registers.x1), numHandles);
|
||||
span waitHandles(reinterpret_cast<KHandle*>(state.ctx->gpr.x1), numHandles);
|
||||
|
||||
for (const auto &handle : waitHandles) {
|
||||
handleStr += fmt::format("* 0x{:X}\n", handle);
|
||||
@ -444,7 +444,7 @@ namespace skyline::kernel::svc {
|
||||
break;
|
||||
|
||||
default: {
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -452,14 +452,14 @@ namespace skyline::kernel::svc {
|
||||
objectTable.push_back(std::static_pointer_cast<type::KSyncObject>(object));
|
||||
}
|
||||
|
||||
auto timeout{state.ctx->registers.x3};
|
||||
auto timeout{state.ctx->gpr.x3};
|
||||
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
|
||||
|
||||
auto start{util::GetTimeNs()};
|
||||
while (true) {
|
||||
if (state.thread->cancelSync) {
|
||||
state.thread->cancelSync = false;
|
||||
state.ctx->registers.w0 = result::Cancelled;
|
||||
state.ctx->gpr.w0 = result::Cancelled;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -467,8 +467,8 @@ namespace skyline::kernel::svc {
|
||||
for (const auto &object : objectTable) {
|
||||
if (object->signalled) {
|
||||
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->registers.w1 = index;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
state.ctx->gpr.w1 = index;
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
@ -476,31 +476,31 @@ namespace skyline::kernel::svc {
|
||||
|
||||
if ((util::GetTimeNs() - start) >= timeout) {
|
||||
state.logger->Debug("svcWaitSynchronization: Wait has timed out");
|
||||
state.ctx->registers.w0 = result::TimedOut;
|
||||
state.ctx->gpr.w0 = result::TimedOut;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CancelSynchronization(DeviceState &state) {
|
||||
void CancelSynchronization(const DeviceState &state) {
|
||||
try {
|
||||
state.process->GetHandle<type::KThread>(state.ctx->registers.w0)->cancelSync = true;
|
||||
state.process->GetHandle<type::KThread>(state.ctx->gpr.w0)->cancelSync = true;
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
|
||||
state.ctx->registers.w0 = result::InvalidHandle;
|
||||
state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->gpr.w0);
|
||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void ArbitrateLock(DeviceState &state) {
|
||||
auto pointer{reinterpret_cast<u32*>(state.ctx->registers.x1)};
|
||||
void ArbitrateLock(const DeviceState &state) {
|
||||
auto pointer{reinterpret_cast<u32*>(state.ctx->gpr.x1)};
|
||||
if (!util::WordAligned(pointer)) {
|
||||
state.logger->Warn("svcArbitrateLock: 'pointer' not word aligned: 0x{:X}", pointer);
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
auto ownerHandle{state.ctx->registers.w0};
|
||||
auto requesterHandle{state.ctx->registers.w2};
|
||||
auto ownerHandle{state.ctx->gpr.w0};
|
||||
auto requesterHandle{state.ctx->gpr.w2};
|
||||
if (requesterHandle != state.thread->handle)
|
||||
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle);
|
||||
|
||||
@ -511,14 +511,14 @@ namespace skyline::kernel::svc {
|
||||
else
|
||||
state.logger->Debug("svcArbitrateLock: Owner handle did not match current owner for mutex or didn't have waiter flag at 0x{:X}", pointer);
|
||||
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void ArbitrateUnlock(DeviceState &state) {
|
||||
auto mutex{reinterpret_cast<u32*>(state.ctx->registers.x0)};
|
||||
void ArbitrateUnlock(const DeviceState &state) {
|
||||
auto mutex{reinterpret_cast<u32*>(state.ctx->gpr.x0)};
|
||||
if (!util::WordAligned(mutex)) {
|
||||
state.logger->Warn("svcArbitrateUnlock: mutex pointer not word aligned: 0x{:X}", mutex);
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -526,54 +526,54 @@ namespace skyline::kernel::svc {
|
||||
|
||||
if (state.process->MutexUnlock(mutex)) {
|
||||
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", mutex);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} else {
|
||||
state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", mutex);
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitProcessWideKeyAtomic(DeviceState &state) {
|
||||
auto mutex{reinterpret_cast<u32*>(state.ctx->registers.x0)};
|
||||
void WaitProcessWideKeyAtomic(const DeviceState &state) {
|
||||
auto mutex{reinterpret_cast<u32*>(state.ctx->gpr.x0)};
|
||||
if (!util::WordAligned(mutex)) {
|
||||
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex pointer not word aligned: 0x{:X}", mutex);
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
auto conditional{reinterpret_cast<void*>(state.ctx->registers.x1)};
|
||||
auto handle{state.ctx->registers.w2};
|
||||
auto conditional{reinterpret_cast<void*>(state.ctx->gpr.x1)};
|
||||
auto handle{state.ctx->gpr.w2};
|
||||
if (handle != state.thread->handle)
|
||||
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle);
|
||||
|
||||
if (!state.process->MutexUnlock(mutex)) {
|
||||
state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mutex);
|
||||
state.ctx->registers.w0 = result::InvalidAddress;
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
auto timeout{state.ctx->registers.x3};
|
||||
auto timeout{state.ctx->gpr.x3};
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mutex, conditional, timeout);
|
||||
|
||||
if (state.process->ConditionalVariableWait(conditional, mutex, timeout)) {
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex");
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} else {
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
|
||||
state.ctx->registers.w0 = result::TimedOut;
|
||||
state.ctx->gpr.w0 = result::TimedOut;
|
||||
}
|
||||
}
|
||||
|
||||
void SignalProcessWideKey(DeviceState &state) {
|
||||
auto conditional{reinterpret_cast<void*>(state.ctx->registers.x0)};
|
||||
auto count{state.ctx->registers.w1};
|
||||
void SignalProcessWideKey(const DeviceState &state) {
|
||||
auto conditional{reinterpret_cast<void*>(state.ctx->gpr.x0)};
|
||||
auto count{state.ctx->gpr.w1};
|
||||
|
||||
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", conditional, count);
|
||||
state.process->ConditionalVariableSignal(conditional, count);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void GetSystemTick(DeviceState &state) {
|
||||
void GetSystemTick(const DeviceState &state) {
|
||||
u64 tick;
|
||||
asm("STR X1, [SP, #-16]!\n\t"
|
||||
"MRS %0, CNTVCT_EL0\n\t"
|
||||
@ -583,36 +583,36 @@ namespace skyline::kernel::svc {
|
||||
"MRS X1, CNTFRQ_EL0\n\t"
|
||||
"UDIV %0, %0, X1\n\t"
|
||||
"LDR X1, [SP], #16" : "=r"(tick));
|
||||
state.ctx->registers.x0 = tick;
|
||||
state.ctx->gpr.x0 = tick;
|
||||
}
|
||||
|
||||
void ConnectToNamedPort(DeviceState &state) {
|
||||
void ConnectToNamedPort(const DeviceState &state) {
|
||||
constexpr u8 portSize = 0x8; //!< The size of a port name string
|
||||
std::string_view port(span(reinterpret_cast<char*>(state.ctx->registers.x1), portSize).as_string(true));
|
||||
std::string_view port(span(reinterpret_cast<char*>(state.ctx->gpr.x1), portSize).as_string(true));
|
||||
|
||||
KHandle handle{};
|
||||
if (port.compare("sm:") >= 0) {
|
||||
handle = state.process->NewHandle<type::KSession>(std::static_pointer_cast<service::BaseService>(state.os->serviceManager.smUserInterface)).handle;
|
||||
} else {
|
||||
state.logger->Warn("svcConnectToNamedPort: Connecting to invalid port: '{}'", port);
|
||||
state.ctx->registers.w0 = result::NotFound;
|
||||
state.ctx->gpr.w0 = result::NotFound;
|
||||
return;
|
||||
}
|
||||
|
||||
state.logger->Debug("svcConnectToNamedPort: Connecting to port '{}' at 0x{:X}", port, handle);
|
||||
|
||||
state.ctx->registers.w1 = handle;
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w1 = handle;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void SendSyncRequest(DeviceState &state) {
|
||||
state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->registers.x0));
|
||||
state.ctx->registers.w0 = Result{};
|
||||
void SendSyncRequest(const DeviceState &state) {
|
||||
state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->gpr.x0));
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void GetThreadId(DeviceState &state) {
|
||||
void GetThreadId(const DeviceState &state) {
|
||||
constexpr KHandle threadSelf{0xFFFF8000}; // The handle used by threads to refer to themselves
|
||||
auto handle{state.ctx->registers.w1};
|
||||
auto handle{state.ctx->gpr.w1};
|
||||
pid_t pid{};
|
||||
|
||||
if (handle != threadSelf)
|
||||
@ -622,24 +622,24 @@ namespace skyline::kernel::svc {
|
||||
|
||||
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
|
||||
|
||||
state.ctx->registers.x1 = static_cast<u64>(pid);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.x1 = static_cast<u64>(pid);
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void OutputDebugString(DeviceState &state) {
|
||||
auto debug{span(reinterpret_cast<u8*>(state.ctx->registers.x0), state.ctx->registers.x1).as_string()};
|
||||
void OutputDebugString(const DeviceState &state) {
|
||||
auto debug{span(reinterpret_cast<u8*>(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()};
|
||||
|
||||
if (debug.back() == '\n')
|
||||
debug.remove_suffix(1);
|
||||
|
||||
state.logger->Info("Debug Output: {}", debug);
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void GetInfo(DeviceState &state) {
|
||||
auto id0{state.ctx->registers.w1};
|
||||
auto handle{state.ctx->registers.w2};
|
||||
auto id1{state.ctx->registers.x3};
|
||||
void GetInfo(const DeviceState &state) {
|
||||
auto id0{state.ctx->gpr.w1};
|
||||
auto handle{state.ctx->gpr.w2};
|
||||
auto id1{state.ctx->gpr.x3};
|
||||
|
||||
u64 out{};
|
||||
|
||||
@ -715,13 +715,13 @@ namespace skyline::kernel::svc {
|
||||
|
||||
default:
|
||||
state.logger->Warn("svcGetInfo: Unimplemented case ID0: {}, ID1: {}", id0, id1);
|
||||
state.ctx->registers.w0 = result::InvalidEnumValue;
|
||||
state.ctx->gpr.w0 = result::InvalidEnumValue;
|
||||
return;
|
||||
}
|
||||
|
||||
state.logger->Debug("svcGetInfo: ID0: {}, ID1: {}, Out: 0x{:X}", id0, id1, out);
|
||||
|
||||
state.ctx->registers.x1 = out;
|
||||
state.ctx->registers.w0 = Result{};
|
||||
state.ctx->gpr.x1 = out;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
}
|
||||
|
@ -43,180 +43,180 @@ namespace skyline {
|
||||
* @brief Sets the process heap to a given size, it can both extend and shrink the heap
|
||||
* @url https://switchbrew.org/wiki/SVC#SetHeapSize
|
||||
*/
|
||||
void SetHeapSize(DeviceState &state);
|
||||
void SetHeapSize(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Change attribute of page-aligned memory region, this is used to turn on/off caching for a given memory area
|
||||
* @url https://switchbrew.org/wiki/SVC#SetMemoryAttribute
|
||||
*/
|
||||
void SetMemoryAttribute(DeviceState &state);
|
||||
void SetMemoryAttribute(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Maps a memory range into a different range, mainly used for adding guard pages around stack
|
||||
* @url https://switchbrew.org/wiki/SVC#SetMemoryAttribute
|
||||
*/
|
||||
void MapMemory(DeviceState &state);
|
||||
void MapMemory(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Unmaps a region that was previously mapped with #MapMemory
|
||||
* @url https://switchbrew.org/wiki/SVC#UnmapMemory
|
||||
*/
|
||||
void UnmapMemory(DeviceState &state);
|
||||
void UnmapMemory(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Query information about an address
|
||||
* @url https://switchbrew.org/wiki/SVC#QueryMemory
|
||||
*/
|
||||
void QueryMemory(DeviceState &state);
|
||||
void QueryMemory(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Exits the current process
|
||||
* @url https://switchbrew.org/wiki/SVC#ExitProcess
|
||||
*/
|
||||
void ExitProcess(DeviceState &state);
|
||||
void ExitProcess(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Create a thread in the current process
|
||||
* @url https://switchbrew.org/wiki/SVC#CreateThread
|
||||
*/
|
||||
void CreateThread(DeviceState &state);
|
||||
void CreateThread(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Starts the thread for the provided handle
|
||||
* @url https://switchbrew.org/wiki/SVC#StartThread
|
||||
*/
|
||||
void StartThread(DeviceState &state);
|
||||
void StartThread(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Exits the current thread
|
||||
* @url https://switchbrew.org/wiki/SVC#ExitThread
|
||||
*/
|
||||
void ExitThread(DeviceState &state);
|
||||
void ExitThread(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Sleep for a specified amount of time, or yield thread
|
||||
* @url https://switchbrew.org/wiki/SVC#SleepThread
|
||||
*/
|
||||
void SleepThread(DeviceState &state);
|
||||
void SleepThread(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Get priority of provided thread handle
|
||||
* @url https://switchbrew.org/wiki/SVC#GetThreadPriority
|
||||
*/
|
||||
void GetThreadPriority(DeviceState &state);
|
||||
void GetThreadPriority(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Set priority of provided thread handle
|
||||
* @url https://switchbrew.org/wiki/SVC#SetThreadPriority
|
||||
*/
|
||||
void SetThreadPriority(DeviceState &state);
|
||||
void SetThreadPriority(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Clears a KEvent of it's signal
|
||||
* @url https://switchbrew.org/wiki/SVC#ClearEvent
|
||||
*/
|
||||
void ClearEvent(DeviceState &state);
|
||||
void ClearEvent(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Maps the block supplied by the handle
|
||||
* @url https://switchbrew.org/wiki/SVC#MapSharedMemory
|
||||
*/
|
||||
void MapSharedMemory(DeviceState &state);
|
||||
void MapSharedMemory(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to a KSharedMemory object
|
||||
* @url https://switchbrew.org/wiki/SVC#CreateTransferMemory
|
||||
*/
|
||||
void CreateTransferMemory(DeviceState &state);
|
||||
void CreateTransferMemory(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Closes the specified handle
|
||||
* @url https://switchbrew.org/wiki/SVC#CloseHandle
|
||||
*/
|
||||
void CloseHandle(DeviceState &state);
|
||||
void CloseHandle(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Resets a particular KEvent or KProcess which is signalled
|
||||
* @url https://switchbrew.org/wiki/SVC#ResetSignal
|
||||
*/
|
||||
void ResetSignal(DeviceState &state);
|
||||
void ResetSignal(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended
|
||||
* @url https://switchbrew.org/wiki/SVC#WaitSynchronization
|
||||
*/
|
||||
void WaitSynchronization(DeviceState &state);
|
||||
void WaitSynchronization(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief If the referenced thread is currently in a synchronization call, that call will be interrupted
|
||||
* @url https://switchbrew.org/wiki/SVC#CancelSynchronization
|
||||
*/
|
||||
void CancelSynchronization(DeviceState &state);
|
||||
void CancelSynchronization(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Locks a specified mutex
|
||||
* @url https://switchbrew.org/wiki/SVC#ArbitrateLock
|
||||
*/
|
||||
void ArbitrateLock(DeviceState &state);
|
||||
void ArbitrateLock(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Unlocks a specified mutex
|
||||
* @url https://switchbrew.org/wiki/SVC#ArbitrateUnlock
|
||||
*/
|
||||
void ArbitrateUnlock(DeviceState &state);
|
||||
void ArbitrateUnlock(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Waits on a process-wide key (Conditional-Variable)
|
||||
* @url https://switchbrew.org/wiki/SVC#WaitProcessWideKeyAtomic
|
||||
*/
|
||||
void WaitProcessWideKeyAtomic(DeviceState &state);
|
||||
void WaitProcessWideKeyAtomic(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Signals a process-wide key (Conditional-Variable)
|
||||
* @url https://switchbrew.org/wiki/SVC#SignalProcessWideKey
|
||||
*/
|
||||
void SignalProcessWideKey(DeviceState &state);
|
||||
void SignalProcessWideKey(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Returns the value of CNTPCT_EL0 on the Switch
|
||||
* @url https://switchbrew.org/wiki/SVC#GetSystemTick
|
||||
*/
|
||||
void GetSystemTick(DeviceState &state);
|
||||
void GetSystemTick(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Connects to a named IPC port
|
||||
* @url https://switchbrew.org/wiki/SVC#ConnectToNamedPort
|
||||
*/
|
||||
void ConnectToNamedPort(DeviceState &state);
|
||||
void ConnectToNamedPort(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Send a synchronous IPC request to a service
|
||||
* @url https://switchbrew.org/wiki/SVC#SendSyncRequest
|
||||
*/
|
||||
void SendSyncRequest(DeviceState &state);
|
||||
void SendSyncRequest(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the PID of a specific thread
|
||||
* @url https://switchbrew.org/wiki/SVC#GetThreadId
|
||||
*/
|
||||
void GetThreadId(DeviceState &state);
|
||||
void GetThreadId(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Outputs a debug string
|
||||
* @url https://switchbrew.org/wiki/SVC#OutputDebugString
|
||||
*/
|
||||
void OutputDebugString(DeviceState &state);
|
||||
void OutputDebugString(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Retrieves a piece of information
|
||||
* @url https://switchbrew.org/wiki/SVC#GetInfo
|
||||
*/
|
||||
void GetInfo(DeviceState &state);
|
||||
void GetInfo(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief The SVC Table maps all SVCs to their corresponding functions
|
||||
*/
|
||||
void static (*SvcTable[0x80])(DeviceState &) = {
|
||||
static std::array<void (*)(const DeviceState &), 0x80> SvcTable{
|
||||
nullptr, // 0x00 (Does not exist)
|
||||
SetHeapSize, // 0x01
|
||||
nullptr, // 0x02
|
||||
|
@ -54,7 +54,7 @@ namespace skyline::kernel::type {
|
||||
throw exception("KPrivateMemory permission updated with a non-page-aligned address: 0x{:X}", ptr);
|
||||
|
||||
// If a static code region has been mapped as writable it needs to be changed to mutable
|
||||
if (memState.value == memory::states::CodeStatic.value && permission.w)
|
||||
if (memState == memory::states::CodeStatic && permission.w)
|
||||
memState = memory::states::CodeMutable;
|
||||
|
||||
state.process->memory.InsertChunk(ChunkDescriptor{
|
||||
|
@ -32,7 +32,7 @@ namespace skyline::kernel::type {
|
||||
|
||||
void KProcess::InitializeHeap() {
|
||||
constexpr size_t DefaultHeapSize{0x200000};
|
||||
heap.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.heap.address), DefaultHeapSize, memory::Permission{true, true, false}, memory::states::Heap);
|
||||
heap = heap.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.heap.address), DefaultHeapSize, memory::Permission{true, true, false}, memory::states::Heap);
|
||||
}
|
||||
|
||||
u8 *KProcess::AllocateTlsSlot() {
|
||||
|
@ -20,27 +20,31 @@ namespace skyline::kernel::type {
|
||||
void KThread::StartThread() {
|
||||
pthread_setname_np(pthread_self(), fmt::format("HOS-{}", id).c_str());
|
||||
|
||||
ctx.sp = stack->ptr + stack->size;
|
||||
if (!ctx.tpidrroEl0)
|
||||
ctx.tpidrroEl0 = parent->AllocateTlsSlot();
|
||||
ctx.nce = state.nce.get();
|
||||
|
||||
ctx.state = &state;
|
||||
state.ctx = &ctx;
|
||||
state.thread = shared_from_this();
|
||||
|
||||
struct sigaction sigact{
|
||||
.sa_sigaction = &NCE::SignalHandler,
|
||||
.sa_sigaction = &nce::NCE::SignalHandler,
|
||||
.sa_flags = SA_SIGINFO,
|
||||
};
|
||||
|
||||
//for (int signal : {SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV})
|
||||
// sigaction(signal, &sigact, nullptr);
|
||||
// sigaction(signal, &sigact, nullptr);
|
||||
|
||||
asm volatile(
|
||||
"MRS X0, TPIDR_EL0\n\t"
|
||||
"MSR TPIDR_EL0, %x0\n\t" // Set TLS to ThreadContext
|
||||
"STR X0, [%x0, #0x2F0]\n\t" // Write ThreadContext::hostTpidrEl0
|
||||
"MOV X0, SP\n\t"
|
||||
"STR X0, [%x0, #16]\n\t" // Store SP in ThreadContext
|
||||
"MOV LR, %x1\n\t" // Store entry in Link Register so it is jumped to on return
|
||||
"MOV X0, %x2\n\t" // Store the argument in X0
|
||||
"MOV X1, %x3\n\t" // Store the thread handle in X1, NCA applications require this
|
||||
"STR X0, [%x0, #0x2F8]\n\t" // Write ThreadContext::hostSp
|
||||
"MOV SP, %x1\n\t" // Replace SP with guest stack
|
||||
"MOV LR, %x2\n\t" // Store entry in Link Register so it is jumped to on return
|
||||
"MOV X0, %x3\n\t" // Store the argument in X0
|
||||
"MOV X1, %x4\n\t" // Store the thread handle in X1, NCA applications require this
|
||||
"MOV X2, XZR\n\t"
|
||||
"MOV X3, XZR\n\t"
|
||||
"MOV X4, XZR\n\t"
|
||||
@ -105,7 +109,7 @@ namespace skyline::kernel::type {
|
||||
"DUP V31.16B, WZR\n\t"
|
||||
"RET"
|
||||
:
|
||||
: "r"(&ctx), "r"(entry), "r"(entryArgument), "r"(handle)
|
||||
: "r"(&ctx), "r"(stack->ptr + stack->size), "r"(entry), "r"(entryArgument), "r"(handle)
|
||||
: "x0", "x1", "lr"
|
||||
);
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nce/guest.h>
|
||||
#include "KSyncObject.h"
|
||||
#include "KPrivateMemory.h"
|
||||
#include "KSharedMemory.h"
|
||||
@ -42,7 +43,7 @@ namespace skyline {
|
||||
/**
|
||||
* @brief KThread manages a single thread of execution which is responsible for running guest code and kernel code which is invoked by the guest
|
||||
*/
|
||||
class KThread : public KSyncObject {
|
||||
class KThread : public KSyncObject, public std::enable_shared_from_this<KThread> {
|
||||
private:
|
||||
KProcess *parent;
|
||||
std::optional<std::thread> thread; //!< If this KThread is backed by a host thread then this'll hold it
|
||||
@ -57,7 +58,7 @@ namespace skyline {
|
||||
size_t id; //!< Index of thread in parent process's KThread vector
|
||||
|
||||
std::shared_ptr<KPrivateMemory> stack;
|
||||
ThreadContext ctx{};
|
||||
nce::ThreadContext ctx{};
|
||||
|
||||
void* entry;
|
||||
u64 entryArgument;
|
||||
|
@ -16,82 +16,66 @@ extern bool Halt;
|
||||
extern jobject Surface;
|
||||
extern skyline::GroupMutex JniMtx;
|
||||
|
||||
namespace skyline {
|
||||
void NCE::KernelThread(pid_t thread) {
|
||||
/*
|
||||
state.jvm->AttachThread();
|
||||
namespace skyline::nce {
|
||||
void NCE::SvcHandler(u16 svc, ThreadContext *ctx) {
|
||||
const auto &state{*ctx->state};
|
||||
try {
|
||||
state.thread = state.process->threads.at(thread);
|
||||
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->kernel.ptr);
|
||||
|
||||
while (true) {
|
||||
asm("yield");
|
||||
|
||||
if (__predict_false(Halt))
|
||||
break;
|
||||
if (__predict_false(!Surface))
|
||||
continue;
|
||||
|
||||
if (state.ctx->state == ThreadState::WaitKernel) {
|
||||
std::lock_guard jniGd(JniMtx);
|
||||
|
||||
if (__predict_false(Halt))
|
||||
break;
|
||||
if (__predict_false(!Surface))
|
||||
continue;
|
||||
|
||||
auto svc{state.ctx->svc};
|
||||
|
||||
try {
|
||||
if (kernel::svc::SvcTable[svc]) {
|
||||
state.logger->Debug("SVC called 0x{:X}", svc);
|
||||
(*kernel::svc::SvcTable[svc])(state);
|
||||
} else {
|
||||
throw exception("Unimplemented SVC 0x{:X}", svc);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
throw exception("{} (SVC: 0x{:X})", e.what(), svc);
|
||||
}
|
||||
|
||||
state.ctx->state = ThreadState::WaitRun;
|
||||
} else if (__predict_false(state.ctx->state == ThreadState::GuestCrash)) {
|
||||
state.logger->Warn("Thread with PID {} has crashed due to signal: {}", thread, strsignal(state.ctx->svc));
|
||||
ThreadTrace();
|
||||
|
||||
state.ctx->state = ThreadState::WaitRun;
|
||||
break;
|
||||
}
|
||||
auto function{kernel::svc::SvcTable[svc]};
|
||||
if (function) {
|
||||
state.logger->Debug("SVC called 0x{:X}", svc);
|
||||
(*function)(state);
|
||||
} else {
|
||||
throw exception("Unimplemented SVC 0x{:X}", svc);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
state.logger->Error(e.what());
|
||||
} catch (...) {
|
||||
state.logger->Error("An unknown exception has occurred");
|
||||
throw exception("{} (SVC: 0x{:X})", e.what(), svc);
|
||||
// Jumps off the edge?
|
||||
// Look into this
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::SignalHandler(int signal, siginfo *, void *context) {
|
||||
ThreadContext *threadCtx;
|
||||
asm("MRS %0, TPIDR_EL0":"=r"(threadCtx));
|
||||
const auto &state{*threadCtx->state};
|
||||
state.logger->Warn("Thread #{} has crashed due to signal: {}", state.thread->id, strsignal(signal));
|
||||
|
||||
std::string raw;
|
||||
std::string trace;
|
||||
std::string cpuContext;
|
||||
|
||||
const auto &ctx{reinterpret_cast<ucontext *>(context)->uc_mcontext};
|
||||
constexpr u16 instructionCount{20}; // The amount of previous instructions to print
|
||||
auto offset{ctx.pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))};
|
||||
span instructions(reinterpret_cast<u32 *>(offset), instructionCount);
|
||||
for (auto &instruction : instructions) {
|
||||
instruction = __builtin_bswap32(instruction);
|
||||
|
||||
if (offset == ctx.pc)
|
||||
trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instruction);
|
||||
else
|
||||
trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instruction);
|
||||
|
||||
raw += fmt::format("{:08X}", instruction);
|
||||
offset += sizeof(u32);
|
||||
}
|
||||
|
||||
if (!Halt) {
|
||||
if (thread == state.process->pid) {
|
||||
JniMtx.lock(GroupMutex::Group::Group2);
|
||||
if (ctx.fault_address)
|
||||
cpuContext += fmt::format("\nFault Address: 0x{:X}", ctx.fault_address);
|
||||
|
||||
state.os->KillThread(thread);
|
||||
Halt = true;
|
||||
if (ctx.sp)
|
||||
cpuContext += fmt::format("\nStack Pointer: 0x{:X}", ctx.sp);
|
||||
|
||||
JniMtx.unlock();
|
||||
} else {
|
||||
state.os->KillThread(thread);
|
||||
}
|
||||
}
|
||||
for (u8 index{}; index < ((sizeof(mcontext_t::regs) / sizeof(u64)) - 2); index += 2)
|
||||
cpuContext += fmt::format("\n{}X{}: 0x{:<16X} {}{}: 0x{:X}", index < 10 ? ' ' : '\0', index, ctx.regs[index], index < 10 ? ' ' : '\0', index + 1, ctx.regs[index]);
|
||||
|
||||
state.jvm->DetachThread();
|
||||
*/
|
||||
state.logger->Debug("Process Trace:{}", trace);
|
||||
state.logger->Debug("Raw Instructions: 0x{}", raw);
|
||||
state.logger->Debug("CPU Context:{}", cpuContext);
|
||||
}
|
||||
|
||||
NCE::NCE(DeviceState &state) : state(state) {}
|
||||
|
||||
NCE::~NCE() {
|
||||
for (auto &thread : threadMap)
|
||||
thread.second->join();
|
||||
}
|
||||
|
||||
void NCE::Execute() {
|
||||
try {
|
||||
while (true) {
|
||||
@ -113,265 +97,184 @@ namespace skyline {
|
||||
}
|
||||
}
|
||||
|
||||
inline ThreadContext *GetContext() {
|
||||
ThreadContext *ctx;
|
||||
asm("MRS %0, TPIDR_EL0":"=r"(ctx));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void NCE::SignalHandler(int signal, const siginfo &info, const ucontext &context) {
|
||||
|
||||
}
|
||||
|
||||
void NCE::SignalHandler(int signal, siginfo *info, void *context) {
|
||||
(GetContext()->nce)->SignalHandler(signal, *info, *reinterpret_cast<ucontext*>(context));
|
||||
}
|
||||
|
||||
void NCE::ThreadTrace(u16 instructionCount, ThreadContext *ctx) {
|
||||
std::string raw;
|
||||
std::string trace;
|
||||
std::string regStr;
|
||||
|
||||
ctx = ctx ? ctx : state.ctx;
|
||||
|
||||
if (instructionCount) {
|
||||
auto offset{ctx->pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))};
|
||||
span instructions(reinterpret_cast<u32 *>(offset), instructionCount);
|
||||
|
||||
for (auto &instruction : instructions) {
|
||||
instruction = __builtin_bswap32(instruction);
|
||||
|
||||
if (offset == ctx->pc)
|
||||
trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instruction);
|
||||
else
|
||||
trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instruction);
|
||||
|
||||
raw += fmt::format("{:08X}", instruction);
|
||||
offset += sizeof(u32);
|
||||
}
|
||||
}
|
||||
|
||||
//if (ctx->faultAddress)
|
||||
// regStr += fmt::format("\nFault Address: 0x{:X}", ctx->faultAddress);
|
||||
|
||||
if (ctx->sp)
|
||||
regStr += fmt::format("\nStack Pointer: 0x{:X}", ctx->sp);
|
||||
|
||||
constexpr u8 numRegisters{31}; //!< The amount of general-purpose registers in ARMv8
|
||||
for (u8 index{}; index < numRegisters - 2; index += 2) {
|
||||
auto xStr{index < 10 ? " X" : "X"};
|
||||
regStr += fmt::format("\n{}{}: 0x{:<16X} {}{}: 0x{:X}", xStr, index, ctx->registers.regs[index], xStr, index + 1, ctx->registers.regs[index + 1]);
|
||||
}
|
||||
|
||||
if (instructionCount) {
|
||||
state.logger->Debug("Process Trace:{}", trace);
|
||||
state.logger->Debug("Raw Instructions: 0x{}", raw);
|
||||
state.logger->Debug("CPU Context:{}", regStr);
|
||||
} else {
|
||||
state.logger->Debug("CPU Context:{}", regStr);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> NCE::PatchCode(std::vector<u8> &code, u64 baseAddress, i64 offset) {
|
||||
std::vector<u32> NCE::PatchCode(std::vector<u8> &code, u64 baseAddress, i64 patchBase) {
|
||||
constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS
|
||||
constexpr u32 TpidrroEl0{0x5E83}; // ID of TPIDRRO_EL0 in MRS
|
||||
constexpr u32 CntfrqEl0{0x5F00}; // ID of CNTFRQ_EL0 in MRS
|
||||
constexpr u32 CntpctEl0{0x5F01}; // ID of CNTPCT_EL0 in MRS
|
||||
constexpr u32 CntvctEl0{0x5F02}; // ID of CNTVCT_EL0 in MRS
|
||||
constexpr u32 TegraX1Freq{19200000}; // The clock frequency of the Tegra X1 (19.2 MHz)
|
||||
constexpr size_t MainSvcTrampolineSize{17};
|
||||
|
||||
size_t index{};
|
||||
std::vector<u32> patch(guest::SaveCtxSize + guest::LoadCtxSize + MainSvcTrampolineSize);
|
||||
|
||||
std::memcpy(patch.data(), reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize * sizeof(u32));
|
||||
index += guest::SaveCtxSize;
|
||||
|
||||
{
|
||||
/* Main SVC Trampoline */
|
||||
|
||||
/* Store LR in 16B of pre-allocated stack */
|
||||
patch[index++] = 0xF90007FE; // STR LR, [SP, #8]
|
||||
|
||||
/* Replace Skyline TLS with host TLS */
|
||||
patch[index++] = 0xD53BD041; // MRS X1, TPIDR_EL0
|
||||
patch[index++] = 0xF9417822; // LDR X2, [X1, #0x2F0] (ThreadContext::hostTpidrEl0)
|
||||
|
||||
/* Replace guest stack with host stack */
|
||||
patch[index++] = 0xD51BD042; // MSR TPIDR_EL0, X2
|
||||
patch[index++] = 0x910003E2; // MOV X2, SP
|
||||
patch[index++] = 0xF9417C23; // LDR X3, [X1, #0x2F8] (ThreadContext::hostSp)
|
||||
patch[index++] = 0x9100007F; // MOV SP, X3
|
||||
|
||||
/* Store Skyline TLS + guest SP on stack */
|
||||
patch[index++] = 0xA9BF0BE1; // STP X1, X2, [SP, #-16]!
|
||||
|
||||
/* Jump to SvcHandler */
|
||||
for (const auto &mov : instr::MoveRegister(regs::X2, reinterpret_cast<u64>(&NCE::SvcHandler)))
|
||||
if (mov)
|
||||
patch[index++] = mov;
|
||||
patch[index++] = 0xD63F0040; // BLR X2
|
||||
|
||||
/* Restore Skyline TLS + guest SP */
|
||||
patch[index++] = 0xA8C10BE1; // LDP X1, X2, [SP], #16
|
||||
patch[index++] = 0xD51BD041; // MSR TPIDR_EL0, X1
|
||||
patch[index++] = 0x9100005F; // MOV SP, X2
|
||||
|
||||
/* Restore LR and Return */
|
||||
patch[index++] = 0xF94007FE; // LDR LR, [SP, #8]
|
||||
patch[index++] = 0xD65F03C0; // RET
|
||||
}
|
||||
|
||||
std::memcpy(patch.data() + index, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32));
|
||||
index += guest::LoadCtxSize;
|
||||
|
||||
u64 frequency;
|
||||
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
|
||||
|
||||
i64 patchOffset{patchBase / i64(sizeof(u32))};
|
||||
u32 *start{reinterpret_cast<u32 *>(code.data())};
|
||||
u32 *end{start + (code.size() / sizeof(u32))};
|
||||
i64 patchOffset{offset};
|
||||
for (u32 *instruction{start}; instruction < end; instruction++) {
|
||||
auto svc{*reinterpret_cast<instr::Svc *>(instruction)};
|
||||
auto mrs{*reinterpret_cast<instr::Mrs *>(instruction)};
|
||||
auto msr{*reinterpret_cast<instr::Msr *>(instruction)};
|
||||
|
||||
std::vector<u32> patch((guest::SaveCtxSize + guest::LoadCtxSize + guest::SvcHandlerSize) / sizeof(u32));
|
||||
if (svc.Verify()) {
|
||||
/* Per-SVC Trampoline */
|
||||
patch.resize(patch.size() + 7);
|
||||
|
||||
std::memcpy(patch.data(), reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize);
|
||||
offset += guest::SaveCtxSize;
|
||||
/* Rewrite SVC with B to trampoline */
|
||||
*instruction = instr::B(patchOffset + index).raw;
|
||||
|
||||
std::memcpy(reinterpret_cast<u8 *>(patch.data()) + guest::SaveCtxSize, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize);
|
||||
offset += guest::LoadCtxSize;
|
||||
/* Save Context */
|
||||
patch[index++] = 0xF81F0FFE; // STR LR, [SP, #-16]!
|
||||
patch[index] = instr::BL(-index).raw;
|
||||
index++;
|
||||
|
||||
std::memcpy(reinterpret_cast<u8 *>(patch.data()) + guest::SaveCtxSize + guest::LoadCtxSize, reinterpret_cast<void *>(&guest::SvcHandler), guest::SvcHandlerSize);
|
||||
offset += guest::SvcHandlerSize;
|
||||
/* Jump to main SVC trampoline */
|
||||
patch[index++] = instr::Movz(regs::W0, static_cast<u16>(svc.value)).raw;
|
||||
patch[index] = instr::BL(guest::SaveCtxSize - index).raw;
|
||||
index++;
|
||||
|
||||
static u64 frequency{};
|
||||
if (!frequency)
|
||||
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
|
||||
/* Restore Context and Return */
|
||||
patch[index] = instr::BL(guest::SaveCtxSize + MainSvcTrampolineSize - index).raw;
|
||||
index++;
|
||||
patch[index++] = 0xF84107FE; // LDR LR, [SP], #16
|
||||
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
|
||||
index++;
|
||||
} else if (mrs.Verify()) {
|
||||
if (mrs.srcReg == TpidrroEl0 || mrs.srcReg == TpidrEl0) {
|
||||
/* Emulated TLS Register Load */
|
||||
patch.resize(patch.size() + ((mrs.destReg != regs::X0) ? 6 : 3));
|
||||
|
||||
for (u32 *address{start}; address < end; address++) {
|
||||
auto instrSvc{reinterpret_cast<instr::Svc *>(address)};
|
||||
auto instrMrs{reinterpret_cast<instr::Mrs *>(address)};
|
||||
auto instrMsr{reinterpret_cast<instr::Msr *>(address)};
|
||||
/* Rewrite MRS with B to trampoline */
|
||||
*instruction = instr::B(patchOffset + index).raw;
|
||||
|
||||
if (instrSvc->Verify()) {
|
||||
// If this is an SVC we need to branch to saveCtx then to the SVC Handler after putting the PC + SVC into X0 and W1 and finally loadCtx before returning to where we were before
|
||||
instr::B bJunc(offset);
|
||||
/* Allocate Scratch Register */
|
||||
if (mrs.destReg != regs::X0)
|
||||
patch[index++] = 0xF81F0FE0; // STR X0, [SP, #-16]!
|
||||
|
||||
constexpr u32 strLr{0xF81F0FFE}; // STR LR, [SP, #-16]!
|
||||
offset += sizeof(strLr);
|
||||
|
||||
instr::BL bSvCtx(patchOffset - offset);
|
||||
offset += sizeof(bSvCtx);
|
||||
|
||||
auto movPc{instr::MoveRegister<u64>(regs::X0, baseAddress + (address - start))};
|
||||
offset += sizeof(u32) * movPc.size();
|
||||
|
||||
instr::Movz movCmd(regs::W1, static_cast<u16>(instrSvc->value));
|
||||
offset += sizeof(movCmd);
|
||||
|
||||
instr::BL bSvcHandler((patchOffset + guest::SaveCtxSize + guest::LoadCtxSize) - offset);
|
||||
offset += sizeof(bSvcHandler);
|
||||
|
||||
instr::BL bLdCtx((patchOffset + guest::SaveCtxSize) - offset);
|
||||
offset += sizeof(bLdCtx);
|
||||
|
||||
constexpr u32 ldrLr{0xF84107FE}; // LDR LR, [SP], #16
|
||||
offset += sizeof(ldrLr);
|
||||
|
||||
instr::B bret(-offset + sizeof(u32));
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bJunc.raw;
|
||||
patch.push_back(strLr);
|
||||
patch.push_back(bSvCtx.raw);
|
||||
for (auto &instr : movPc)
|
||||
patch.push_back(instr);
|
||||
patch.push_back(movCmd.raw);
|
||||
patch.push_back(bSvcHandler.raw);
|
||||
patch.push_back(bLdCtx.raw);
|
||||
patch.push_back(ldrLr);
|
||||
patch.push_back(bret.raw);
|
||||
} else if (instrMrs->Verify()) {
|
||||
if (instrMrs->srcReg == TpidrroEl0 || instrMrs->srcReg == TpidrEl0) {
|
||||
// If this moves TPIDR(RO)_EL0 into a register then we retrieve the value of our virtual TPIDR(RO)_EL0 from TLS and write it to the register
|
||||
instr::B bJunc(offset);
|
||||
|
||||
u32 strX0{};
|
||||
if (instrMrs->destReg != regs::X0) {
|
||||
strX0 = 0xF81F0FE0; // STR X0, [SP, #-16]!
|
||||
offset += sizeof(strX0);
|
||||
}
|
||||
|
||||
constexpr u32 mrsX0{0xD53BD040}; // MRS X0, TPIDR_EL0
|
||||
offset += sizeof(mrsX0);
|
||||
|
||||
u32 ldrTls;
|
||||
if (instrMrs->srcReg == TpidrroEl0)
|
||||
ldrTls = 0xF9408000; // LDR X0, [X0, #256] (ThreadContext::tpidrroEl0)
|
||||
/* Retrieve emulated TLS register from ThreadContext */
|
||||
patch[index++] = 0xD53BD040; // MRS X0, TPIDR_EL0
|
||||
if (mrs.srcReg == TpidrroEl0)
|
||||
patch[index++] = 0xF9418000; // LDR X0, [X0, #0x300] (ThreadContext::tpidrroEl0)
|
||||
else
|
||||
ldrTls = 0xF9408400; // LDR X0, [X0, #264] (ThreadContext::tpidrEl0)
|
||||
patch[index++] = 0xF9418400; // LDR X0, [X0, #0x308] (ThreadContext::tpidrEl0)
|
||||
|
||||
offset += sizeof(ldrTls);
|
||||
|
||||
u32 movXn{};
|
||||
u32 ldrX0{};
|
||||
if (instrMrs->destReg != regs::X0) {
|
||||
movXn = instr::Mov(regs::X(instrMrs->destReg), regs::X0).raw;
|
||||
offset += sizeof(movXn);
|
||||
|
||||
ldrX0 = 0xF84107E0; // LDR X0, [SP], #16
|
||||
offset += sizeof(ldrX0);
|
||||
}
|
||||
|
||||
instr::B bret(-offset + sizeof(u32));
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bJunc.raw;
|
||||
if (strX0)
|
||||
patch.push_back(strX0);
|
||||
patch.push_back(mrsX0);
|
||||
patch.push_back(ldrTls);
|
||||
if (movXn)
|
||||
patch.push_back(movXn);
|
||||
if (ldrX0)
|
||||
patch.push_back(ldrX0);
|
||||
patch.push_back(bret.raw);
|
||||
} else if (frequency != TegraX1Freq) {
|
||||
// These deal with changing the timer registers, we only do this if the clock frequency doesn't match the X1's clock frequency
|
||||
if (instrMrs->srcReg == CntpctEl0) {
|
||||
// If this moves CNTPCT_EL0 into a register then call RescaleClock to rescale the device's clock to the X1's clock frequency and write result to register
|
||||
instr::B bJunc(offset);
|
||||
offset += guest::RescaleClockSize;
|
||||
|
||||
instr::Ldr ldr(0xF94003E0); // LDR XOUT, [SP]
|
||||
ldr.destReg = instrMrs->destReg;
|
||||
offset += sizeof(ldr);
|
||||
|
||||
constexpr u32 addSp{0x910083FF}; // ADD SP, SP, #32
|
||||
offset += sizeof(addSp);
|
||||
|
||||
instr::B bret(-offset + sizeof(u32));
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bJunc.raw;
|
||||
auto size{patch.size()};
|
||||
patch.resize(size + (guest::RescaleClockSize / sizeof(u32)));
|
||||
std::memcpy(patch.data() + size, reinterpret_cast<void *>(&guest::RescaleClock), guest::RescaleClockSize);
|
||||
patch.push_back(ldr.raw);
|
||||
patch.push_back(addSp);
|
||||
patch.push_back(bret.raw);
|
||||
} else if (instrMrs->srcReg == CntfrqEl0) {
|
||||
// If this moves CNTFRQ_EL0 into a register then move the Tegra X1's clock frequency into the register (Rather than the host clock frequency)
|
||||
instr::B bJunc(offset);
|
||||
|
||||
auto movFreq{instr::MoveRegister<u32>(static_cast<regs::X>(instrMrs->destReg), TegraX1Freq)};
|
||||
offset += sizeof(u32) * movFreq.size();
|
||||
|
||||
instr::B bret(-offset + sizeof(u32));
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bJunc.raw;
|
||||
for (auto &instr : movFreq)
|
||||
patch.push_back(instr);
|
||||
patch.push_back(bret.raw);
|
||||
/* Restore Scratch Register and Return */
|
||||
if (mrs.destReg != regs::X0) {
|
||||
patch[index++] = instr::Mov(regs::X(mrs.destReg), regs::X0).raw;
|
||||
patch[index++] = 0xF84107E0; // LDR X0, [SP], #16
|
||||
}
|
||||
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
|
||||
index++;
|
||||
} else {
|
||||
// If the host clock frequency is the same as the Tegra X1's clock frequency
|
||||
if (instrMrs->srcReg == CntpctEl0) {
|
||||
// If this moves CNTPCT_EL0 into a register, change the instruction to move CNTVCT_EL0 instead as Linux or most other OSes don't allow access to CNTPCT_EL0 rather only CNTVCT_EL0 can be accessed from userspace
|
||||
*address = instr::Mrs(CntvctEl0, regs::X(instrMrs->destReg)).raw;
|
||||
if (frequency != TegraX1Freq) {
|
||||
if (mrs.srcReg == CntpctEl0) {
|
||||
/* Physical Counter Load Emulation (With Rescaling) */
|
||||
patch.resize(patch.size() + guest::RescaleClockSize + 3);
|
||||
|
||||
/* Rewrite MRS with B to trampoline */
|
||||
*instruction = instr::B(patchOffset + index).raw;
|
||||
|
||||
/* Rescale host clock */
|
||||
std::memcpy(patch.data() + index, reinterpret_cast<void *>(&guest::RescaleClock), guest::RescaleClockSize);
|
||||
index += guest::RescaleClockSize;
|
||||
|
||||
/* Load result from stack into destination register */
|
||||
instr::Ldr ldr(0xF94003E0); // LDR XOUT, [SP]
|
||||
ldr.destReg = mrs.destReg;
|
||||
patch[index++] = ldr.raw;
|
||||
|
||||
/* Free 32B stack allocation by RescaleClock and Return */
|
||||
patch[index++] = {0x910083FF}; // ADD SP, SP, #32
|
||||
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
|
||||
index++;
|
||||
} else if (mrs.srcReg == CntfrqEl0) {
|
||||
/* Physical Counter Frequency Load Emulation */
|
||||
patch.resize(patch.size() + 3);
|
||||
|
||||
/* Rewrite MRS with B to trampoline */
|
||||
*instruction = instr::B(patchOffset + index).raw;
|
||||
|
||||
/* Write back Tegra X1 Counter Frequency and Return */
|
||||
for (const auto &mov : instr::MoveRegister(regs::X(mrs.destReg), TegraX1Freq))
|
||||
patch[index++] = mov;
|
||||
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
|
||||
index++;
|
||||
}
|
||||
} else if (mrs.srcReg == CntpctEl0) {
|
||||
/* Physical Counter Load Emulation (Without Rescaling) */
|
||||
// We just convert CNTPCT_EL0 -> CNTVCT_EL0 as Linux doesn't allow access to the physical counter
|
||||
*instruction = instr::Mrs(CntvctEl0, regs::X(mrs.destReg)).raw;
|
||||
}
|
||||
}
|
||||
} else if (instrMsr->Verify()) {
|
||||
if (instrMsr->destReg == TpidrEl0) {
|
||||
// If this moves a register into TPIDR_EL0 then we retrieve the value of the register and write it to our virtual TPIDR_EL0 in TLS
|
||||
instr::B bJunc(offset);
|
||||
} else if (msr.Verify()) {
|
||||
if (msr.destReg == TpidrEl0) {
|
||||
/* Emulated TLS Register Store */
|
||||
patch.resize(patch.size() + 6);
|
||||
|
||||
// Used to avoid conflicts as we cannot read the source register from the stack
|
||||
bool x0x1{instrMrs->srcReg != regs::X0 && instrMrs->srcReg != regs::X1};
|
||||
/* Rewrite MSR with B to trampoline */
|
||||
*instruction = instr::B(patchOffset + index).raw;
|
||||
|
||||
// Push two registers to stack that can be used to load the TLS and arguments into
|
||||
u32 pushXn{x0x1 ? 0xA9BF07E0 : 0xA9BF0FE2}; // STP X(0/2), X(1/3), [SP, #-16]!
|
||||
offset += sizeof(pushXn);
|
||||
/* Allocate Scratch Registers */
|
||||
bool x0x1{mrs.srcReg != regs::X0 && mrs.srcReg != regs::X1};
|
||||
patch[index++] = x0x1 ? 0xA9BF07E0 : 0xA9BF0FE2; // STP X(0/2), X(1/3), [SP, #-16]!
|
||||
|
||||
u32 loadRealTls{x0x1 ? 0xD53BD040 : 0xD53BD042}; // MRS X(0/2), TPIDR_EL0
|
||||
offset += sizeof(loadRealTls);
|
||||
/* Store new TLS value into ThreadContext */
|
||||
patch[index++] = x0x1 ? 0xD53BD040 : 0xD53BD042; // MRS X(0/2), TPIDR_EL0
|
||||
patch[index++] = instr::Mov(x0x1 ? regs::X1 : regs::X3, regs::X(msr.srcReg)).raw;
|
||||
patch[index++] = x0x1 ? 0xF9018401 : 0xF9018403; // STR X(1/3), [X0, #0x308] (ThreadContext::tpidrEl0)
|
||||
|
||||
instr::Mov moveParam(x0x1 ? regs::X1 : regs::X3, regs::X(instrMsr->srcReg));
|
||||
offset += sizeof(moveParam);
|
||||
|
||||
u32 storeEmuTls{x0x1 ? 0xF9008401 : 0xF9008403}; // STR X(1/3), [X0, #264] (ThreadContext::tpidrEl0)
|
||||
offset += sizeof(storeEmuTls);
|
||||
|
||||
u32 popXn{x0x1 ? 0xA8C107E0 : 0xA8C10FE2}; // LDP X(0/2), X(1/3), [SP], #16
|
||||
offset += sizeof(popXn);
|
||||
|
||||
instr::B bret(-offset + sizeof(u32));
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bJunc.raw;
|
||||
patch.push_back(pushXn);
|
||||
patch.push_back(loadRealTls);
|
||||
patch.push_back(moveParam.raw);
|
||||
patch.push_back(storeEmuTls);
|
||||
patch.push_back(popXn);
|
||||
patch.push_back(bret.raw);
|
||||
/* Restore Scratch Registers and Return */
|
||||
patch[index++] = x0x1 ? 0xA8C107E0 : 0xA8C10FE2; // LDP X(0/2), X(1/3), [SP], #16
|
||||
patch[index] = instr::B(-(patchOffset + index - 1)).raw;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
offset -= sizeof(u32);
|
||||
patchOffset -= sizeof(u32);
|
||||
patchOffset--;
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
@ -6,47 +6,28 @@
|
||||
#include "common.h"
|
||||
#include <sys/wait.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace skyline::nce {
|
||||
/**
|
||||
* @brief The NCE (Native Code Execution) class is responsible for managing the state of catching instructions and directly controlling processes/threads
|
||||
*/
|
||||
class NCE {
|
||||
private:
|
||||
DeviceState &state;
|
||||
std::unordered_map<pid_t, std::shared_ptr<std::thread>> threadMap; //!< This maps all of the host threads to their corresponding kernel thread
|
||||
|
||||
/**
|
||||
* @brief The event loop of a kernel thread managing a guest thread
|
||||
* @param thread The PID of the thread to manage
|
||||
*/
|
||||
void KernelThread(pid_t thread);
|
||||
static void SvcHandler(u16 svc, ThreadContext* ctx);
|
||||
|
||||
public:
|
||||
NCE(DeviceState &state);
|
||||
static void SignalHandler(int signal, siginfo *info, void *context);
|
||||
|
||||
~NCE();
|
||||
NCE(DeviceState &state);
|
||||
|
||||
void Execute();
|
||||
|
||||
void SignalHandler(int signal, const siginfo &info, const ucontext &ucontext);
|
||||
|
||||
/**
|
||||
* @brief A delegator to the real signal handler after restoring context
|
||||
*/
|
||||
static void SignalHandler(int signal, siginfo *info, void *context);
|
||||
|
||||
/**
|
||||
* @brief Prints out a trace and the CPU context
|
||||
* @param instructionCount The amount of previous instructions to print (Can be 0)
|
||||
*/
|
||||
void ThreadTrace(u16 instructionCount = 10, ThreadContext *ctx = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Patches specific parts of the code
|
||||
* @param code A vector with the code to be patched
|
||||
* @brief Generates a patch section for the supplied code
|
||||
* @param baseAddress The address at which the code is mapped
|
||||
* @param offset The offset of the code block from the base address
|
||||
* @param patchBase The offset of the patch section from the base address
|
||||
*/
|
||||
std::vector<u32> PatchCode(std::vector<u8> &code, u64 baseAddress, i64 offset);
|
||||
std::vector<u32> PatchCode(std::vector<u8> &code, u64 baseAddress, i64 patchBase);
|
||||
};
|
||||
}
|
||||
|
@ -4,46 +4,104 @@
|
||||
.text
|
||||
.global SaveCtx
|
||||
SaveCtx:
|
||||
STR LR, [SP, #-16]!
|
||||
/* Prepare Scratch Register */
|
||||
STR LR, [SP, #8] // It is assumed that 8B of stack memory has already been allocated before calling this
|
||||
MRS LR, TPIDR_EL0
|
||||
STP X0, X1, [LR, #16]
|
||||
STP X2, X3, [LR, #32]
|
||||
STP X4, X5, [LR, #48]
|
||||
STP X6, X7, [LR, #64]
|
||||
STP X8, X9, [LR, #80]
|
||||
STP X10, X11, [LR, #96]
|
||||
STP X12, X13, [LR, #112]
|
||||
STP X14, X15, [LR, #128]
|
||||
STP X16, X17, [LR, #144]
|
||||
STP X18, X19, [LR, #160]
|
||||
STP X20, X21, [LR, #176]
|
||||
STP X22, X23, [LR, #192]
|
||||
STP X24, X25, [LR, #208]
|
||||
STP X26, X27, [LR, #224]
|
||||
STP X28, X29, [LR, #240]
|
||||
LDR LR, [SP], #16
|
||||
|
||||
/* Store GP Registers */
|
||||
STP X0, X1, [LR, #0]
|
||||
STP X2, X3, [LR, #16]
|
||||
STP X4, X5, [LR, #32]
|
||||
STP X6, X7, [LR, #48]
|
||||
STP X8, X9, [LR, #64]
|
||||
STP X10, X11, [LR, #80]
|
||||
STP X12, X13, [LR, #96]
|
||||
STP X14, X15, [LR, #112]
|
||||
STP X16, X17, [LR, #128]
|
||||
STP X18, X19, [LR, #144]
|
||||
STP X20, X21, [LR, #160]
|
||||
STP X22, X23, [LR, #176]
|
||||
STP X24, X25, [LR, #192]
|
||||
STP X26, X27, [LR, #208]
|
||||
STP X28, X29, [LR, #224]
|
||||
|
||||
/* Store FP Registers */
|
||||
STP Q0, Q1, [LR, #240]
|
||||
STP Q2, Q3, [LR, #272]
|
||||
STP Q4, Q5, [LR, #304]
|
||||
STP Q6, Q7, [LR, #336]
|
||||
STP Q8, Q9, [LR, #368]
|
||||
STP Q10, Q11, [LR, #400]
|
||||
STP Q12, Q13, [LR, #432]
|
||||
STP Q14, Q15, [LR, #464]
|
||||
STP Q16, Q17, [LR, #496]
|
||||
STP Q18, Q19, [LR, #528]
|
||||
STP Q20, Q21, [LR, #560]
|
||||
STP Q22, Q23, [LR, #592]
|
||||
STP Q24, Q25, [LR, #624]
|
||||
STP Q26, Q27, [LR, #656]
|
||||
STP Q28, Q29, [LR, #688]
|
||||
STP Q30, Q31, [LR, #720]
|
||||
|
||||
/* Store FPCR/FPSR */
|
||||
MRS X0, FPSR
|
||||
STR W0, [LR, #744]
|
||||
MRS X1, FPCR
|
||||
STR W1, [LR, #748]
|
||||
|
||||
/* Restore Scratch Register */
|
||||
LDR LR, [SP, #8]
|
||||
RET
|
||||
|
||||
.global LoadCtx
|
||||
LoadCtx:
|
||||
STR LR, [SP, #-16]!
|
||||
/* Prepare Scratch Register */
|
||||
STR LR, [SP, #8] // It is assumed that 8B of stack memory has already been allocated before calling this
|
||||
MRS LR, TPIDR_EL0
|
||||
LDP X0, X1, [LR, #16]
|
||||
LDP X2, X3, [LR, #32]
|
||||
LDP X4, X5, [LR, #48]
|
||||
LDP X6, X7, [LR, #64]
|
||||
LDP X8, X9, [LR, #80]
|
||||
LDP X10, X11, [LR, #96]
|
||||
LDP X12, X13, [LR, #112]
|
||||
LDP X14, X15, [LR, #128]
|
||||
LDP X16, X17, [LR, #144]
|
||||
LDP X18, X19, [LR, #160]
|
||||
LDP X20, X21, [LR, #176]
|
||||
LDP X22, X23, [LR, #192]
|
||||
LDP X24, X25, [LR, #208]
|
||||
LDP X26, X27, [LR, #224]
|
||||
LDP X28, X29, [LR, #240]
|
||||
LDR LR, [SP], #16
|
||||
|
||||
/* Load FP Registers */
|
||||
LDP Q0, Q1, [LR, #240]
|
||||
LDP Q2, Q3, [LR, #272]
|
||||
LDP Q4, Q5, [LR, #304]
|
||||
LDP Q6, Q7, [LR, #336]
|
||||
LDP Q8, Q9, [LR, #368]
|
||||
LDP Q10, Q11, [LR, #400]
|
||||
LDP Q12, Q13, [LR, #432]
|
||||
LDP Q14, Q15, [LR, #464]
|
||||
LDP Q16, Q17, [LR, #496]
|
||||
LDP Q18, Q19, [LR, #528]
|
||||
LDP Q20, Q21, [LR, #560]
|
||||
LDP Q22, Q23, [LR, #592]
|
||||
LDP Q24, Q25, [LR, #624]
|
||||
LDP Q26, Q27, [LR, #656]
|
||||
LDP Q28, Q29, [LR, #688]
|
||||
LDP Q30, Q31, [LR, #720]
|
||||
|
||||
/* Store FPCR/FPSR */
|
||||
MRS X0, FPSR
|
||||
STR W0, [LR, #744]
|
||||
MRS X1, FPCR
|
||||
STR W1, [LR, #748]
|
||||
|
||||
/* Load GP Registers */
|
||||
LDP X0, X1, [LR, #0]
|
||||
LDP X2, X3, [LR, #16]
|
||||
LDP X4, X5, [LR, #32]
|
||||
LDP X6, X7, [LR, #48]
|
||||
LDP X8, X9, [LR, #64]
|
||||
LDP X10, X11, [LR, #80]
|
||||
LDP X12, X13, [LR, #96]
|
||||
LDP X14, X15, [LR, #112]
|
||||
LDP X16, X17, [LR, #128]
|
||||
LDP X18, X19, [LR, #144]
|
||||
LDP X20, X21, [LR, #160]
|
||||
LDP X22, X23, [LR, #176]
|
||||
LDP X24, X25, [LR, #192]
|
||||
LDP X26, X27, [LR, #208]
|
||||
LDP X28, X29, [LR, #224]
|
||||
|
||||
/* Restore Scratch Register */
|
||||
LDR LR, [SP, #8]
|
||||
RET
|
||||
|
||||
.global RescaleClock
|
||||
|
@ -3,39 +3,160 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "guest_common.h"
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace guest {
|
||||
constexpr size_t SaveCtxSize{20 * sizeof(u32)}; //!< The size of the SaveCtx function in 32-bit ARMv8 instructions
|
||||
constexpr size_t LoadCtxSize{20 * sizeof(u32)}; //!< The size of the LoadCtx function in 32-bit ARMv8 instructions
|
||||
constexpr size_t RescaleClockSize{16 * sizeof(u32)}; //!< The size of the RescaleClock function in 32-bit ARMv8 instructions
|
||||
#ifdef NDEBUG
|
||||
constexpr size_t SvcHandlerSize{225 * sizeof(u32)}; //!< The size of the SvcHandler (Release) function in 32-bit ARMv8 instructions
|
||||
#else
|
||||
constexpr size_t SvcHandlerSize{400 * sizeof(u32)}; //!< The size of the SvcHandler (Debug) function in 32-bit ARMv8 instructions
|
||||
#endif
|
||||
struct DeviceState;
|
||||
namespace nce {
|
||||
/**
|
||||
* @brief The state of all the general purpose registers in the guest
|
||||
* @note Read about ARMv8 registers here: https://developer.arm.com/architectures/learn-the-architecture/armv8-a-instruction-set-architecture/registers-in-aarch64-general-purpose-registers
|
||||
* @note X30 or LR is not provided as it is reserved for other uses
|
||||
*/
|
||||
union GpRegisters {
|
||||
std::array<u64, 30> regs;
|
||||
struct {
|
||||
u64 x0;
|
||||
u64 x1;
|
||||
u64 x2;
|
||||
u64 x3;
|
||||
u64 x4;
|
||||
u64 x5;
|
||||
u64 x6;
|
||||
u64 x7;
|
||||
u64 x8;
|
||||
u64 x9;
|
||||
u64 x10;
|
||||
u64 x11;
|
||||
u64 x12;
|
||||
u64 x13;
|
||||
u64 x14;
|
||||
u64 x15;
|
||||
u64 x16;
|
||||
u64 x17;
|
||||
u64 x18;
|
||||
u64 x19;
|
||||
u64 x20;
|
||||
u64 x21;
|
||||
u64 x22;
|
||||
u64 x23;
|
||||
u64 x24;
|
||||
u64 x25;
|
||||
u64 x26;
|
||||
u64 x27;
|
||||
u64 x28;
|
||||
u64 x29;
|
||||
};
|
||||
struct {
|
||||
u32 w0;
|
||||
u32 __w0__;
|
||||
u32 w1;
|
||||
u32 __w1__;
|
||||
u32 w2;
|
||||
u32 __w2__;
|
||||
u32 w3;
|
||||
u32 __w3__;
|
||||
u32 w4;
|
||||
u32 __w4__;
|
||||
u32 w5;
|
||||
u32 __w5__;
|
||||
u32 w6;
|
||||
u32 __w6__;
|
||||
u32 w7;
|
||||
u32 __w7__;
|
||||
u32 w8;
|
||||
u32 __w8__;
|
||||
u32 w9;
|
||||
u32 __w9__;
|
||||
u32 w10;
|
||||
u32 __w10__;
|
||||
u32 w11;
|
||||
u32 __w11__;
|
||||
u32 w12;
|
||||
u32 __w12__;
|
||||
u32 w13;
|
||||
u32 __w13__;
|
||||
u32 w14;
|
||||
u32 __w14__;
|
||||
u32 w15;
|
||||
u32 __w15__;
|
||||
u32 w16;
|
||||
u32 __w16__;
|
||||
u32 w17;
|
||||
u32 __w17__;
|
||||
u32 w18;
|
||||
u32 __w18__;
|
||||
u32 w19;
|
||||
u32 __w19__;
|
||||
u32 w20;
|
||||
u32 __w20__;
|
||||
u32 w21;
|
||||
u32 __w21__;
|
||||
u32 w22;
|
||||
u32 __w22__;
|
||||
u32 w23;
|
||||
u32 __w23__;
|
||||
u32 w24;
|
||||
u32 __w24__;
|
||||
u32 w25;
|
||||
u32 __w25__;
|
||||
u32 w26;
|
||||
u32 __w26__;
|
||||
u32 w27;
|
||||
u32 __w27__;
|
||||
u32 w28;
|
||||
u32 __w28__;
|
||||
u32 w29;
|
||||
u32 __w29__;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Saves the context from CPU registers into TLS
|
||||
* @brief The state of all the floating point (and SIMD) registers in the guest
|
||||
* @note FPSR/FPCR are 64-bit system registers but only the lower 32-bits are used
|
||||
*/
|
||||
extern "C" void SaveCtx(void);
|
||||
union FpRegisters {
|
||||
std::array<u128, 32> regs;
|
||||
u32 fpsr;
|
||||
u32 fpcr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Loads the context from TLS into CPU registers
|
||||
* @brief A per-thread context for guest threads
|
||||
* @note It's stored in TPIDR_EL0 while running the guest
|
||||
*/
|
||||
extern "C" void LoadCtx(void);
|
||||
struct ThreadContext {
|
||||
GpRegisters gpr;
|
||||
FpRegisters fpr;
|
||||
u8 *hostTpidrEl0; //!< Host TLS TPIDR_EL0, this must be swapped to prior to calling any CXX functions
|
||||
u8 *hostSp; //!< Host Stack Pointer, same as above
|
||||
u8 *tpidrroEl0; //!< Emulated HOS TPIDRRO_EL0
|
||||
u8 *tpidrEl0; //!< Emulated HOS TPIDR_EL0
|
||||
const DeviceState *state;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Rescales the clock to Tegra X1 levels and puts the output on stack
|
||||
*/
|
||||
extern "C" __noreturn void RescaleClock(void);
|
||||
namespace guest {
|
||||
constexpr size_t SaveCtxSize{39}; //!< The size of the SaveCtx function in 32-bit ARMv8 instructions
|
||||
constexpr size_t LoadCtxSize{39}; //!< The size of the LoadCtx function in 32-bit ARMv8 instructions
|
||||
constexpr size_t RescaleClockSize{16}; //!< The size of the RescaleClock function in 32-bit ARMv8 instructions
|
||||
|
||||
/**
|
||||
* @brief Handles all SVC calls
|
||||
* @param pc The address of PC when the call was being done
|
||||
* @param svc The SVC ID of the SVC being called
|
||||
*/
|
||||
void SvcHandler(u64 pc, u16 svc);
|
||||
/**
|
||||
* @brief Saves the context from CPU registers into TLS
|
||||
* @note Assumes that 8B is reserved at an offset of 8B from SP
|
||||
*/
|
||||
extern "C" void SaveCtx(void);
|
||||
|
||||
/**
|
||||
* @brief Loads the context from TLS into CPU registers
|
||||
* @note Assumes that 8B is reserved at an offset of 8B from SP
|
||||
*/
|
||||
extern "C" void LoadCtx(void);
|
||||
|
||||
/**
|
||||
* @brief Rescales the host clock to Tegra X1 levels
|
||||
* @note Output is on stack with the stack pointer offset 32B from the initial point
|
||||
*/
|
||||
extern "C" __noreturn void RescaleClock(void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace skyline::nce {
|
||||
namespace regs {
|
||||
enum X { X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30 };
|
||||
enum W { W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, W16, W17, W18, W19, W20, W21, W22, W23, W24, W25, W26, W27, W28, W29, W30 };
|
||||
@ -112,10 +112,10 @@ namespace skyline {
|
||||
struct B {
|
||||
public:
|
||||
/**
|
||||
* @param offset The relative offset to branch to (Should be 32-bit aligned)
|
||||
* @param offset The relative offset to branch to (In 32-bit units)
|
||||
*/
|
||||
constexpr B(i64 offset) {
|
||||
this->offset = static_cast<i32>(offset / sizeof(u32));
|
||||
constexpr B(i32 offset) {
|
||||
this->offset = offset;
|
||||
sig = 0x5;
|
||||
}
|
||||
|
||||
@ -146,10 +146,10 @@ namespace skyline {
|
||||
struct BL {
|
||||
public:
|
||||
/**
|
||||
* @param offset The relative offset to branch to (Should be 32-bit aligned)
|
||||
* @param offset The relative offset to branch to (In 32-bit units)
|
||||
*/
|
||||
constexpr BL(i64 offset) {
|
||||
this->offset = static_cast<i32>(offset / sizeof(u32));
|
||||
constexpr BL(i32 offset) {
|
||||
this->offset = offset;
|
||||
sig = 0x25;
|
||||
}
|
||||
|
||||
@ -288,20 +288,26 @@ namespace skyline {
|
||||
* @param destination The destination register of the operation
|
||||
* @param value The value to insert into the register
|
||||
* @return A array with the instructions to insert the value
|
||||
* @note 0 is returned for any instruction that isn't required
|
||||
*/
|
||||
template<typename Type>
|
||||
constexpr std::array<u32, sizeof(Type) / sizeof(u16)> MoveRegister(regs::X destination, Type value) {
|
||||
std::array<u32, sizeof(Type) / sizeof(u16)> instructions;
|
||||
|
||||
auto valuePointer{reinterpret_cast<u16 *>(&value)};
|
||||
bool zeroed{};
|
||||
u8 offset{};
|
||||
|
||||
for (auto &instruction : instructions) {
|
||||
if (offset)
|
||||
instruction = instr::Movk(destination, *(valuePointer + offset), offset).raw;
|
||||
else
|
||||
instruction = instr::Movz(destination, *(valuePointer + offset), offset).raw;
|
||||
|
||||
auto offsetValue{*(valuePointer + offset)};
|
||||
if (offsetValue) {
|
||||
if (zeroed) {
|
||||
instruction = instr::Movk(destination, offsetValue, offset).raw;
|
||||
} else {
|
||||
instruction = instr::Movz(destination, offsetValue, offset).raw;
|
||||
zeroed = true;
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user