mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-22 20:29:15 +01:00
Move from ptrace to junction branching and make kernel multithreaded
This commit is a huge step in the direction of better performance, as we move from ptrace to junction branching and have kernel call overhead similar to that of a native kernel call! In addition, this sets the base for the kernel to go fully multi-threaded. However, the kernel is currently not thread-safe and therefore this commit currently causes a crash.
This commit is contained in:
parent
b84859d352
commit
970dde8c27
@ -22,7 +22,8 @@ include_directories(${source_DIR}/skyline)
|
||||
add_library(skyline SHARED
|
||||
${source_DIR}/main.cpp
|
||||
${source_DIR}/skyline/common.cpp
|
||||
${source_DIR}/skyline/guest.S
|
||||
${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/gpu.cpp
|
||||
|
@ -5,18 +5,15 @@
|
||||
|
||||
namespace skyline {
|
||||
void Mutex::lock() {
|
||||
while (flag.exchange(true, std::memory_order_relaxed));
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
while (flag.test_and_set(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
void Mutex::unlock() {
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
flag.store(false, std::memory_order_relaxed);
|
||||
flag.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
bool Mutex::try_lock() {
|
||||
bool fal = false;
|
||||
return flag.compare_exchange_strong(fal, true, std::memory_order_relaxed);
|
||||
return !flag.test_and_set(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
Settings::Settings(const int preferenceFd) {
|
||||
@ -90,10 +87,13 @@ namespace skyline {
|
||||
logFile.flush();
|
||||
}
|
||||
|
||||
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &thisProcess, std::shared_ptr<kernel::type::KThread> &thisThread, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)
|
||||
: os(os), jvmManager(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)), process(thisProcess), thread(thisThread) {
|
||||
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), jvmManager(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::move(std::make_shared<NCE>(*this));
|
||||
gpu = std::move(std::make_shared<gpu::GPU>(*this));
|
||||
}
|
||||
|
||||
thread_local std::shared_ptr<kernel::type::KThread> DeviceState::thread = 0;
|
||||
thread_local ThreadContext* DeviceState::ctx = 0;
|
||||
}
|
||||
|
@ -11,24 +11,13 @@
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <android/native_window.h>
|
||||
#include <jni.h>
|
||||
#include "nce/guest_common.h"
|
||||
|
||||
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
|
||||
using handle_t = u32; //!< The type of a kernel handle
|
||||
|
||||
namespace constant {
|
||||
@ -36,8 +25,7 @@ namespace skyline {
|
||||
constexpr u64 BaseAddr = 0x8000000; //!< The address space base
|
||||
constexpr u64 MapAddr = BaseAddr + 0x80000000; //!< The address of the map region
|
||||
constexpr u64 HeapAddr = MapAddr + 0x1000000000; //!< The address of the heap region
|
||||
constexpr u64 BaseSize = 0x7FF8000000; //!< The size of the address space
|
||||
constexpr u64 BaseEnd = BaseAddr + BaseSize; //!< The end of the address space
|
||||
constexpr u64 BaseEnd = 0x7FFFFFFFFF; //!< The end of the address space
|
||||
constexpr u64 MapSize = 0x1000000000; //!< The size of the map region
|
||||
constexpr u64 TotalPhyMem = 0xF8000000; // ~4 GB of RAM
|
||||
constexpr size_t DefStackSize = 0x1E8480; //!< The default amount of stack: 2 MB
|
||||
@ -52,6 +40,8 @@ namespace skyline {
|
||||
constexpr u16 SvcLast = 0x7F; //!< The index of the last SVC
|
||||
constexpr u16 BrkRdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
|
||||
constexpr u32 TpidrroEl0 = 0x5E83; //!< ID of TPIDRRO_EL0 in MRS
|
||||
constexpr u32 CntfrqEl0 = 0x5F00; //!< ID of CNTFRQ_EL0 in MRS
|
||||
constexpr u32 TegraX1Freq = 0x124F800; //!< The clock frequency of the Tegra X1
|
||||
constexpr u32 CntpctEl0 = 0x5F01; //!< ID of CNTPCT_EL0 in MRS
|
||||
constexpr u32 CntvctEl0 = 0x5F02; //!< ID of CNTVCT_EL0 in MRS
|
||||
// Kernel
|
||||
@ -106,18 +96,11 @@ namespace skyline {
|
||||
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
|
||||
};
|
||||
|
||||
/**
|
||||
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
|
||||
*/
|
||||
enum class Xreg { 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 class Wreg { 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 class Sreg { Sp, Pc, PState };
|
||||
|
||||
/**
|
||||
* @brief The Mutex class is a wrapper around an atomic bool used for synchronization
|
||||
*/
|
||||
class Mutex {
|
||||
std::atomic<bool> flag{false}; //!< This atomic bool holds the status of the lock
|
||||
std::atomic_flag flag = ATOMIC_FLAG_INIT; //!< An atomic flag to hold the state of the mutex
|
||||
|
||||
public:
|
||||
/**
|
||||
@ -303,11 +286,12 @@ namespace skyline {
|
||||
* @brief This struct is used to hold the state of a device
|
||||
*/
|
||||
struct DeviceState {
|
||||
DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &thisProcess, std::shared_ptr<kernel::type::KThread> &thisThread, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger);
|
||||
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);
|
||||
|
||||
kernel::OS *os; //!< This holds a reference to the OS class
|
||||
std::shared_ptr<kernel::type::KProcess> &process; //!< This holds a reference to the process object
|
||||
std::shared_ptr<kernel::type::KThread> &thread; //!< This holds a reference to the current thread object
|
||||
thread_local static std::shared_ptr<kernel::type::KThread> thread; //!< This holds a reference to the current thread object
|
||||
thread_local static ThreadContext* ctx; //!< This holds the context of the thread
|
||||
std::shared_ptr<NCE> nce; //!< This holds a reference to the NCE class
|
||||
std::shared_ptr<gpu::GPU> gpu; //!< This holds a reference to the GPU class
|
||||
std::shared_ptr<JvmManager> jvmManager; //!< This holds a reference to the JvmManager class
|
||||
|
@ -24,7 +24,7 @@ namespace skyline::gpu {
|
||||
|
||||
void GPU::Loop() {
|
||||
if (surfaceUpdate) {
|
||||
if (state.jvmManager->CheckNull(Surface))
|
||||
if (Surface == nullptr)
|
||||
return;
|
||||
window = ANativeWindow_fromSurface(state.jvmManager->GetEnv(), Surface);
|
||||
ANativeWindow_acquire(window);
|
||||
@ -33,7 +33,7 @@ namespace skyline::gpu {
|
||||
format = ANativeWindow_getFormat(window);
|
||||
surfaceUpdate = true;
|
||||
} else
|
||||
surfaceUpdate = state.jvmManager->CheckNull(Surface);
|
||||
surfaceUpdate = (Surface == nullptr);
|
||||
if (!bufferQueue.displayQueue.empty()) {
|
||||
auto &buffer = bufferQueue.displayQueue.front();
|
||||
bufferQueue.displayQueue.pop();
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "kernel/types/KEvent.h"
|
||||
#include "gpu/display.h"
|
||||
#include "gpu/devices/nvdevice.h"
|
||||
#include <android/native_window.h>
|
||||
|
||||
namespace skyline::gpu {
|
||||
/**
|
||||
|
@ -1,46 +0,0 @@
|
||||
.text
|
||||
.global saveCtx
|
||||
saveCtx:
|
||||
STR LR, [SP, #-16]!
|
||||
MRS LR, TPIDR_EL0
|
||||
#LDR LR, [LR]
|
||||
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]
|
||||
LDR LR, [SP], #16
|
||||
RET
|
||||
|
||||
.global loadCtx
|
||||
loadCtx:
|
||||
STR LR, [SP, #-16]!
|
||||
MRS LR, TPIDR_EL0
|
||||
#LDR LR, [LR]
|
||||
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]
|
||||
LDR LR, [SP], #16
|
||||
RET
|
@ -1,179 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace skyline {
|
||||
namespace guest {
|
||||
constexpr size_t saveCtxSize = 20 * sizeof(u32);
|
||||
constexpr size_t loadCtxSize = 20 * sizeof(u32);
|
||||
extern "C" void saveCtx(void);
|
||||
extern "C" void loadCtx(void);
|
||||
}
|
||||
|
||||
namespace instr {
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a BRK instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
|
||||
*/
|
||||
struct Brk {
|
||||
/**
|
||||
* @brief Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
|
||||
* @param value The immediate value of the instruction
|
||||
*/
|
||||
explicit Brk(u16 value) {
|
||||
start = 0x0; // First 5 bits of a BRK instruction are 0
|
||||
this->value = value;
|
||||
end = 0x6A1; // Last 11 bits of a BRK instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid BRK instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (start == 0x0 && end == 0x6A1);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
u16 end : 11;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(Brk) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
|
||||
*/
|
||||
struct Svc {
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid SVC instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (start == 0x1 && end == 0x6A0);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
u16 end : 11;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(Svc) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
|
||||
*/
|
||||
struct Mrs {
|
||||
/**
|
||||
* @brief Creates a MRS instruction, used for generating BRK opcodes
|
||||
* @param srcReg The source system register
|
||||
* @param dstReg The destination Xn register
|
||||
*/
|
||||
Mrs(u32 srcReg, u8 dstReg) {
|
||||
this->srcReg = srcReg;
|
||||
this->dstReg = dstReg;
|
||||
end = 0xD53; // Last 12 bits of a MRS instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid MRS instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (end == 0xD53);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
u8 dstReg : 5;
|
||||
u32 srcReg : 15;
|
||||
u16 end : 12;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(Mrs) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a B instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch.
|
||||
*/
|
||||
struct B {
|
||||
public:
|
||||
explicit B(i64 offset) {
|
||||
this->offset = static_cast<i32>(offset / 4);
|
||||
end = 0x5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the offset of the instruction
|
||||
* @return The offset encoded within the instruction
|
||||
*/
|
||||
inline i32 Offset() {
|
||||
return offset * 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid Branch instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (end == 0x5);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
i32 offset : 26;
|
||||
u8 end : 6;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(B) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a BL instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch.
|
||||
*/
|
||||
struct BL {
|
||||
public:
|
||||
explicit BL(i64 offset) {
|
||||
this->offset = static_cast<i32>(offset / 4);
|
||||
end = 0x25;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the offset of the instruction
|
||||
* @return The offset encoded within the instruction
|
||||
*/
|
||||
inline i32 Offset() {
|
||||
return offset * 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid Branch instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (end == 0x85);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
i32 offset : 26;
|
||||
u8 end : 6;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(BL) == sizeof(u32));
|
||||
}
|
||||
}
|
@ -3,10 +3,10 @@
|
||||
|
||||
namespace skyline::kernel::svc {
|
||||
void SetHeapSize(DeviceState &state) {
|
||||
const u32 size = state.nce->GetRegister(Wreg::W1);
|
||||
const u32 size = state.ctx->registers.w1;
|
||||
if(size%constant::HeapSizeDiv != 0) {
|
||||
state.nce->SetRegister(Xreg::X1, 0);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvSize);
|
||||
state.ctx->registers.x1 = 0;
|
||||
state.ctx->registers.w0 = constant::status::InvSize;
|
||||
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
|
||||
return;
|
||||
}
|
||||
@ -19,29 +19,29 @@ namespace skyline::kernel::svc {
|
||||
state.process->UnmapPrivateRegion(memory::Region::Heap);
|
||||
heap = state.process->MapPrivateRegion(constant::HeapAddr, size, {true, true, false}, memory::Type::Heap, memory::Region::Heap).item;
|
||||
}
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.nce->SetRegister(Xreg::X1, heap->address);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.ctx->registers.x1 = heap->address;
|
||||
state.logger->Debug("svcSetHeapSize: Allocated at 0x{:X} for 0x{:X} bytes", heap->address, heap->size);
|
||||
}
|
||||
|
||||
void SetMemoryAttribute(DeviceState &state) {
|
||||
const u64 addr = state.nce->GetRegister(Xreg::X0);
|
||||
const u64 addr = state.ctx->registers.x0;
|
||||
if((addr & (PAGE_SIZE - 1U))) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcSetMemoryAttribute: 'address' not page aligned: {}", addr);
|
||||
return;
|
||||
}
|
||||
const u64 size = state.nce->GetRegister(Xreg::X1);
|
||||
const u64 size = state.ctx->registers.x1;
|
||||
if((size & (PAGE_SIZE - 1U)) || !size) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvSize);
|
||||
state.ctx->registers.w0 = constant::status::InvSize;
|
||||
state.logger->Warn("svcSetMemoryAttribute: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
u32 mask = state.nce->GetRegister(Wreg::W2);
|
||||
u32 value = state.nce->GetRegister(Wreg::W3);
|
||||
u32 mask = state.ctx->registers.w2;
|
||||
u32 value = state.ctx->registers.w3;
|
||||
u32 maskedValue = mask | value;
|
||||
if(maskedValue != mask) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvCombination);
|
||||
state.ctx->registers.w0 = constant::status::InvCombination;
|
||||
state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask, value);
|
||||
return;
|
||||
}
|
||||
@ -63,17 +63,17 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", addr);
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} for 0x{:X} bytes", !attribute.isUncached, addr, size);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void QueryMemory(DeviceState &state) {
|
||||
memory::MemoryInfo memInfo{};
|
||||
u64 addr = state.nce->GetRegister(Xreg::X2);
|
||||
u64 addr = state.ctx->registers.x2;
|
||||
bool found = false;
|
||||
for (const auto&[address, region] : state.process->memoryMap) {
|
||||
if (addr >= address && addr < (address + region->size)) {
|
||||
@ -112,8 +112,8 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
}
|
||||
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Size: 0x{:X}, Type: {}, Is Uncached: {}, Permissions: {}{}{}", memInfo.baseAddress, memInfo.size, memInfo.type, static_cast<bool>(memInfo.memoryAttribute.isUncached), memInfo.r ? "R" : "-", memInfo.w ? "W" : "-", memInfo.x ? "X" : "-");
|
||||
state.process->WriteMemory<memory::MemoryInfo>(memInfo, state.nce->GetRegister(Xreg::X0));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.process->WriteMemory<memory::MemoryInfo>(memInfo, state.ctx->registers.x0);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void ExitProcess(DeviceState &state) {
|
||||
@ -122,30 +122,30 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
|
||||
void CreateThread(DeviceState &state) {
|
||||
u64 entryAddr = state.nce->GetRegister(Xreg::X1);
|
||||
u64 entryArg = state.nce->GetRegister(Xreg::X2);
|
||||
u64 stackTop = state.nce->GetRegister(Xreg::X3);
|
||||
u8 priority = static_cast<u8>(state.nce->GetRegister(Wreg::W4));
|
||||
u64 entryAddr = state.ctx->registers.x1;
|
||||
u64 entryArg = state.ctx->registers.x2;
|
||||
u64 stackTop = state.ctx->registers.x3;
|
||||
u8 priority = static_cast<u8>(state.ctx->registers.w4);
|
||||
if((priority < constant::PriorityNin.first) && (priority > constant::PriorityNin.second)) { // NOLINT(misc-redundant-expression)
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
|
||||
return;
|
||||
}
|
||||
auto thread = state.process->CreateThread(entryAddr, entryArg, stackTop, priority);
|
||||
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, PID: {})", thread->handle, entryAddr, entryArg, stackTop, priority, thread->pid);
|
||||
state.nce->SetRegister(Wreg::W1, thread->handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w1 = thread->handle;
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void StartThread(DeviceState &state) {
|
||||
auto handle = state.nce->GetRegister(Wreg::W0);
|
||||
auto handle = state.ctx->registers.w0;
|
||||
try {
|
||||
auto thread = state.process->GetHandle<type::KThread>(handle);
|
||||
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->pid);
|
||||
thread->Start();
|
||||
} catch (const std::exception&) {
|
||||
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +155,7 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
|
||||
void SleepThread(DeviceState &state) {
|
||||
auto in = state.nce->GetRegister(Xreg::X0);
|
||||
auto in = state.ctx->registers.x0;
|
||||
switch (in) {
|
||||
case 0:
|
||||
case 1:
|
||||
@ -171,102 +171,102 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
|
||||
void GetThreadPriority(DeviceState &state) {
|
||||
auto handle = state.nce->GetRegister(Wreg::W0);
|
||||
auto handle = state.ctx->registers.w0;
|
||||
try {
|
||||
auto priority = state.process->GetHandle<type::KThread>(handle)->priority;
|
||||
state.nce->SetRegister(Wreg::W1, priority);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w1 = priority;
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority);
|
||||
} catch (const std::exception&) {
|
||||
state.logger->Warn("svcGetThreadPriority: 'handle' invalid: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void SetThreadPriority(DeviceState &state) {
|
||||
auto handle = state.nce->GetRegister(Wreg::W0);
|
||||
auto priority = state.nce->GetRegister(Wreg::W1);
|
||||
auto handle = state.ctx->registers.w0;
|
||||
auto priority = state.ctx->registers.w1;
|
||||
try {
|
||||
state.process->GetHandle<type::KThread>(handle)->UpdatePriority(static_cast<u8>(priority));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority);
|
||||
} catch (const std::exception&) {
|
||||
state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void MapSharedMemory(DeviceState &state) {
|
||||
try {
|
||||
auto object = state.process->GetHandle<type::KSharedMemory>(state.nce->GetRegister(Wreg::W0));
|
||||
u64 addr = state.nce->GetRegister(Xreg::X1);
|
||||
auto object = state.process->GetHandle<type::KSharedMemory>(state.ctx->registers.w0);
|
||||
u64 addr = state.ctx->registers.x1;
|
||||
if ((addr & (PAGE_SIZE - 1U))) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcMapSharedMemory: 'address' not page aligned: 0x{:X}", addr);
|
||||
return;
|
||||
}
|
||||
const u64 size = state.nce->GetRegister(Xreg::X2);
|
||||
const u64 size = state.ctx->registers.x2;
|
||||
if ((size & (PAGE_SIZE - 1U)) || !size) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvSize);
|
||||
state.ctx->registers.w0 = constant::status::InvSize;
|
||||
state.logger->Warn("svcMapSharedMemory: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
u32 perm = state.nce->GetRegister(Wreg::W3);
|
||||
u32 perm = state.ctx->registers.w3;
|
||||
memory::Permission permission = *reinterpret_cast<memory::Permission *>(&perm);
|
||||
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.nce->SetRegister(Wreg::W0, constant::status::InvPermission);
|
||||
state.ctx->registers.w0 = constant::status::InvPermission;
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcMapSharedMemory: Mapping shared memory at 0x{:X} for {} bytes ({}{}{})", addr, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
|
||||
object->Map(addr, size, permission);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.nce->GetRegister(Wreg::W0));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateTransferMemory(DeviceState &state) {
|
||||
u64 addr = state.nce->GetRegister(Xreg::X1);
|
||||
u64 addr = state.ctx->registers.x1;
|
||||
if ((addr & (PAGE_SIZE - 1U))) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcCreateTransferMemory: 'address' not page aligned: {}", addr);
|
||||
return;
|
||||
}
|
||||
u64 size = state.nce->GetRegister(Xreg::X2);
|
||||
u64 size = state.ctx->registers.x2;
|
||||
if ((size & (PAGE_SIZE - 1U)) || !size) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvSize);
|
||||
state.ctx->registers.w0 = constant::status::InvSize;
|
||||
state.logger->Warn("svcCreateTransferMemory: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
u32 perm = state.nce->GetRegister(Wreg::W3);
|
||||
u32 perm = state.ctx->registers.w3;
|
||||
memory::Permission permission = *reinterpret_cast<memory::Permission *>(&perm);
|
||||
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.nce->SetRegister(Wreg::W0, constant::status::InvPermission);
|
||||
state.ctx->registers.w0 = constant::status::InvPermission;
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcCreateTransferMemory: Creating transfer memory at 0x{:X} for {} bytes ({}{}{})", addr, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
|
||||
auto shmem = state.process->NewHandle<type::KTransferMemory>(state.process->pid, addr, size, permission);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.nce->SetRegister(Wreg::W1, shmem.handle);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.ctx->registers.w1 = shmem.handle;
|
||||
}
|
||||
|
||||
void CloseHandle(DeviceState &state) {
|
||||
auto handle = static_cast<handle_t>(state.nce->GetRegister(Wreg::W0));
|
||||
auto handle = static_cast<handle_t>(state.ctx->registers.w0);
|
||||
try {
|
||||
state.process->handleTable.erase(handle);
|
||||
state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
} catch(const std::exception&) {
|
||||
state.logger->Warn("svcCloseHandle: 'handle' invalid: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetSignal(DeviceState &state) {
|
||||
auto handle = state.nce->GetRegister(Wreg::W0);
|
||||
auto handle = state.ctx->registers.w0;
|
||||
try {
|
||||
auto &object = state.process->handleTable.at(handle);
|
||||
switch (object->objectType) {
|
||||
@ -278,27 +278,27 @@ namespace skyline::kernel::svc {
|
||||
break;
|
||||
default: {
|
||||
state.logger->Warn("svcResetSignal: 'handle' type invalid: 0x{:X} ({})", handle, object->objectType);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
} catch(const std::out_of_range&) {
|
||||
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitSynchronization(DeviceState &state) {
|
||||
auto numHandles = state.nce->GetRegister(Wreg::W2);
|
||||
auto numHandles = state.ctx->registers.w2;
|
||||
if (numHandles > constant::MaxSyncHandles) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::MaxHandles);
|
||||
state.ctx->registers.w0 = constant::status::MaxHandles;
|
||||
return;
|
||||
}
|
||||
std::vector<handle_t> waitHandles(numHandles);
|
||||
state.process->ReadMemory(waitHandles.data(), state.nce->GetRegister(Xreg::X1), numHandles * sizeof(handle_t));
|
||||
state.process->ReadMemory(waitHandles.data(), state.ctx->registers.x1, numHandles * sizeof(handle_t));
|
||||
std::string handleStr;
|
||||
uint index{};
|
||||
for (const auto &handle : waitHandles) {
|
||||
@ -311,7 +311,7 @@ namespace skyline::kernel::svc {
|
||||
case type::KType::KSession:
|
||||
break;
|
||||
default: {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvHandle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
state.thread->ClearWaitObjects();
|
||||
return;
|
||||
}
|
||||
@ -319,17 +319,17 @@ namespace skyline::kernel::svc {
|
||||
auto syncObject = std::static_pointer_cast<type::KSyncObject>(object);
|
||||
if (syncObject->signalled) {
|
||||
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.nce->SetRegister(Wreg::W1, index);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.ctx->registers.w1 = index;
|
||||
state.thread->ClearWaitObjects();
|
||||
return;
|
||||
}
|
||||
state.thread->waitObjects.push_back(syncObject);
|
||||
syncObject->waitThreads.emplace_back(state.thread->pid, index);
|
||||
}
|
||||
auto timeout = state.nce->GetRegister(Xreg::X3);
|
||||
auto timeout = state.ctx->registers.x3;
|
||||
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
|
||||
if (state.nce->GetRegister(Xreg::X3) != std::numeric_limits<u64>::max())
|
||||
if (state.ctx->registers.x3 != std::numeric_limits<u64>::max())
|
||||
state.thread->timeout = GetCurrTimeNs() + timeout;
|
||||
else
|
||||
state.thread->timeout = 0;
|
||||
@ -337,44 +337,44 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
|
||||
void ArbitrateLock(DeviceState &state) {
|
||||
auto addr = state.nce->GetRegister(Xreg::X1);
|
||||
auto addr = state.ctx->registers.x1;
|
||||
if((addr & ((1UL << WORD_BIT) - 1U))) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: {}", addr);
|
||||
return;
|
||||
}
|
||||
auto handle = state.nce->GetRegister(Wreg::W2);
|
||||
auto handle = state.ctx->registers.w2;
|
||||
if (handle != state.thread->handle)
|
||||
throw exception("svcArbitrateLock: Called from another thread");
|
||||
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X} for thread 0x{:X}", addr, handle);
|
||||
state.process->MutexLock(addr);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void ArbitrateUnlock(DeviceState &state) {
|
||||
auto addr = state.nce->GetRegister(Xreg::X0);
|
||||
auto addr = state.ctx->registers.x0;
|
||||
if((addr & ((1UL << WORD_BIT) - 1U))) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: {}", addr);
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", addr);
|
||||
state.process->MutexUnlock(addr);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void WaitProcessWideKeyAtomic(DeviceState &state) {
|
||||
auto mtxAddr = state.nce->GetRegister(Xreg::X0);
|
||||
auto mtxAddr = state.ctx->registers.x0;
|
||||
if((mtxAddr & ((1UL << WORD_BIT) - 1U))) {
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::InvAddress);
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: {}", mtxAddr);
|
||||
return;
|
||||
}
|
||||
auto handle = state.nce->GetRegister(Wreg::W2);
|
||||
auto handle = state.ctx->registers.w2;
|
||||
if (handle != state.thread->handle)
|
||||
throw exception("svcWaitProcessWideKeyAtomic: Called from another thread");
|
||||
state.process->MutexUnlock(mtxAddr);
|
||||
auto condAddr = state.nce->GetRegister(Xreg::X1);
|
||||
auto condAddr = state.ctx->registers.x1;
|
||||
auto &cvarVec = state.process->condVarMap[condAddr];
|
||||
for (auto thread = cvarVec.begin();; thread++) {
|
||||
if ((*thread)->priority < state.thread->priority) {
|
||||
@ -385,17 +385,17 @@ namespace skyline::kernel::svc {
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto timeout = state.nce->GetRegister(Xreg::X3);
|
||||
auto timeout = state.ctx->registers.x3;
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x:{:X}, Timeout: {} ns", mtxAddr, condAddr, timeout);
|
||||
state.thread->status = type::KThread::Status::WaitCondVar;
|
||||
state.thread->timeout = GetCurrTimeNs() + timeout;
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void SignalProcessWideKey(DeviceState &state) {
|
||||
auto address = state.nce->GetRegister(Xreg::X0);
|
||||
auto count = state.nce->GetRegister(Wreg::W1);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
auto address = state.ctx->registers.x0;
|
||||
auto count = state.ctx->registers.w1;
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
if (!state.process->condVarMap.count(address)) {
|
||||
state.logger->Debug("svcSignalProcessWideKey: No Conditional-Variable at 0x{:X}", address);
|
||||
return;
|
||||
@ -420,55 +420,55 @@ namespace skyline::kernel::svc {
|
||||
"MRS X1, CNTFRQ_EL0\n\t"
|
||||
"UDIV %0, %0, X1\n\t"
|
||||
"LDR X1, [SP], #16" : "=r"(tick));
|
||||
state.nce->SetRegister(Xreg::X0, tick);
|
||||
state.ctx->registers.x0 = tick;
|
||||
}
|
||||
|
||||
void ConnectToNamedPort(DeviceState &state) {
|
||||
char port[constant::PortSize + 1]{0};
|
||||
state.process->ReadMemory(port, state.nce->GetRegister(Xreg::X1), constant::PortSize);
|
||||
state.process->ReadMemory(port, state.ctx->registers.x1, constant::PortSize);
|
||||
handle_t handle{};
|
||||
if (std::strcmp(port, "sm:") == 0)
|
||||
handle = state.os->serviceManager.NewSession(service::Service::sm);
|
||||
else {
|
||||
state.logger->Warn("svcConnectToNamedPort: Connecting to invalid port: '{}'", port);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::NotFound);
|
||||
state.ctx->registers.w0 = constant::status::NotFound;
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcConnectToNamedPort: Connecting to port '{}' at 0x{:X}", port, handle);
|
||||
state.nce->SetRegister(Wreg::W1, handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w1 = handle;
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void SendSyncRequest(DeviceState &state) {
|
||||
state.os->serviceManager.SyncRequestHandler(static_cast<handle_t>(state.nce->GetRegister(Xreg::X0)));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.os->serviceManager.SyncRequestHandler(static_cast<handle_t>(state.ctx->registers.x0));
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void GetThreadId(DeviceState &state) {
|
||||
pid_t pid{};
|
||||
auto handle = state.nce->GetRegister(Wreg::W1);
|
||||
auto handle = state.ctx->registers.w1;
|
||||
if (handle != constant::ThreadSelf) {
|
||||
pid = state.process->GetHandle<type::KThread>(handle)->pid;
|
||||
} else
|
||||
pid = state.thread->pid;
|
||||
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
|
||||
state.nce->SetRegister(Xreg::X1, static_cast<u64>(pid));
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.x1 = static_cast<u64>(pid);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void OutputDebugString(DeviceState &state) {
|
||||
std::string debug(state.nce->GetRegister(Xreg::X1), '\0');
|
||||
state.process->ReadMemory(debug.data(), state.nce->GetRegister(Xreg::X0), state.nce->GetRegister(Xreg::X1));
|
||||
std::string debug(state.ctx->registers.x1, '\0');
|
||||
state.process->ReadMemory(debug.data(), state.ctx->registers.x0, state.ctx->registers.x1);
|
||||
if(debug.back() == '\n')
|
||||
debug.pop_back();
|
||||
state.logger->Info("Debug Output: {}", debug);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void GetInfo(DeviceState &state) {
|
||||
auto id0 = state.nce->GetRegister(Wreg::W1);
|
||||
auto handle = state.nce->GetRegister(Wreg::W2);
|
||||
auto id1 = state.nce->GetRegister(Xreg::X3);
|
||||
auto id0 = state.ctx->registers.w1;
|
||||
auto handle = state.ctx->registers.w2;
|
||||
auto id1 = state.ctx->registers.x3;
|
||||
u64 out{};
|
||||
switch (id0) {
|
||||
case constant::infoState::AllowedCpuIdBitmask:
|
||||
@ -499,7 +499,7 @@ namespace skyline::kernel::svc {
|
||||
out = constant::BaseAddr;
|
||||
break;
|
||||
case constant::infoState::AddressSpaceSize:
|
||||
out = constant::BaseSize;
|
||||
out = constant::BaseEnd;
|
||||
break;
|
||||
case constant::infoState::StackRegionBaseAddr:
|
||||
out = state.thread->stackTop;
|
||||
@ -524,11 +524,11 @@ namespace skyline::kernel::svc {
|
||||
break;
|
||||
default:
|
||||
state.logger->Warn("svcGetInfo: Unimplemented case ID0: {}, ID1: {}", id0, id1);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Unimpl);
|
||||
state.ctx->registers.w0 = constant::status::Unimpl;
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcGetInfo: ID0: {}, ID1: {}, Out: 0x{:X}", id0, id1, out);
|
||||
state.nce->SetRegister(Xreg::X1, out);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.ctx->registers.x1 = out;
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,22 @@
|
||||
#include "KPrivateMemory.h"
|
||||
#include "KProcess.h"
|
||||
#include <nce.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
u64 MapPrivateFunc(u64 dstAddress, u64 srcAddress, size_t size, u64 perms) {
|
||||
dstAddress = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(dstAddress), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((dstAddress) ? MAP_FIXED : 0), -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
if (srcAddress) {
|
||||
memcpy(reinterpret_cast<void *>(dstAddress), reinterpret_cast<const void *>(srcAddress), size);
|
||||
mprotect(reinterpret_cast<void *>(srcAddress), size, PROT_NONE);
|
||||
}
|
||||
return dstAddress;
|
||||
}
|
||||
|
||||
KPrivateMemory::KPrivateMemory(const DeviceState &state, u64 dstAddress, u64 srcAddress, size_t size, memory::Permission permission, const memory::Type type, const pid_t pid) : state(state), address(dstAddress), size(size), permission(permission), type(type), KObject(state, KType::KPrivateMemory) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = dstAddress;
|
||||
fregs.regs[1] = srcAddress;
|
||||
fregs.regs[2] = size;
|
||||
fregs.regs[3] = static_cast<u64>(permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, pid ? pid : state.process->pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
KPrivateMemory::KPrivateMemory(const DeviceState &state, u64 dstAddress, size_t size, memory::Permission permission, const memory::Type type, std::shared_ptr<KThread> thread) : state(state), address(dstAddress), size(size), permission(permission), type(type), KObject(state, KType::KPrivateMemory) {
|
||||
Registers fregs{};
|
||||
fregs.x0 = dstAddress;
|
||||
fregs.x1 = size;
|
||||
fregs.x2 = static_cast<u64>(permission.Get());
|
||||
fregs.x3 = static_cast<u64>(MAP_PRIVATE | MAP_ANONYMOUS | ((dstAddress) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
fregs.x4 = static_cast<u64>(-1);
|
||||
fregs.x8 = __NR_mmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, thread);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while mapping private region in child process");
|
||||
if (!this->address)
|
||||
this->address = fregs.regs[0];
|
||||
this->address = fregs.x0;
|
||||
}
|
||||
|
||||
u64 RemapPrivateFunc(u64 address, size_t oldSize, size_t size, u64 flags) {
|
||||
@ -30,15 +24,16 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
u64 KPrivateMemory::Resize(size_t newSize, bool canMove) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = newSize;
|
||||
fregs.regs[3] = canMove ? MREMAP_MAYMOVE : 0;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, state.process->pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x2 = newSize;
|
||||
fregs.x3 = canMove ? MREMAP_MAYMOVE : 0;
|
||||
fregs.x8 = __NR_mremap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while remapping private region in child process");
|
||||
address = fregs.regs[0];
|
||||
address = fregs.x0;
|
||||
size = newSize;
|
||||
return address;
|
||||
}
|
||||
@ -48,12 +43,13 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
void KPrivateMemory::UpdatePermission(memory::Permission permission) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64>(permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, state.process->pid);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x2 = static_cast<u64>(permission.Get());
|
||||
fregs.x8 = __NR_mprotect;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while updating private region's permissions in child process");
|
||||
this->permission = permission;
|
||||
}
|
||||
@ -82,11 +78,11 @@ namespace skyline::kernel::type {
|
||||
|
||||
KPrivateMemory::~KPrivateMemory() {
|
||||
try {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, state.process->pid);
|
||||
} catch (const std::exception &) {
|
||||
}
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x8 = __NR_munmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread);
|
||||
} catch (const std::exception &) {}
|
||||
}
|
||||
};
|
||||
|
@ -23,13 +23,12 @@ namespace skyline::kernel::type {
|
||||
/**
|
||||
* @param state The state of the device
|
||||
* @param dstAddress The address to map to (If NULL then an arbitrary address is picked)
|
||||
* @param srcAddress The address to map from (If NULL then no copy is performed)
|
||||
* @param size The size of the allocation
|
||||
* @param permission The permissions for the allocated memory
|
||||
* @param type The type of the memory
|
||||
* @param pid The PID of the me
|
||||
* @param thread The thread to execute the calls on
|
||||
*/
|
||||
KPrivateMemory(const DeviceState &state, u64 dstAddress, u64 srcAddress, size_t size, memory::Permission permission, const memory::Type type, const pid_t pid=0);
|
||||
KPrivateMemory(const DeviceState &state, u64 dstAddress, size_t size, memory::Permission permission, const memory::Type type, std::shared_ptr<KThread> thread = 0);
|
||||
|
||||
/**
|
||||
* @brief Remap a chunk of memory as to change the size occupied by it
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <os.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KProcess::TlsPage::TlsPage(u64 address) : address(address) {}
|
||||
@ -29,7 +30,7 @@ namespace skyline::kernel::type {
|
||||
if (!tlsPage->Full())
|
||||
return tlsPage->ReserveSlot();
|
||||
}
|
||||
auto tlsMem = NewHandle<KPrivateMemory>(0, 0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal, pid).item;
|
||||
auto tlsMem = NewHandle<KPrivateMemory>(0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal, threadMap.at(pid)).item;
|
||||
memoryMap[tlsMem->address] = tlsMem;
|
||||
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
|
||||
auto &tlsPage = tlsPages.back();
|
||||
@ -38,9 +39,12 @@ namespace skyline::kernel::type {
|
||||
return tlsPage->ReserveSlot();
|
||||
}
|
||||
|
||||
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize) : pid(pid), mainThreadStackSz(stackSize), KSyncObject(state, KType::KProcess) {
|
||||
state.nce->WaitRdy(pid);
|
||||
threadMap[pid] = NewHandle<KThread>(pid, entryPoint, 0x0, stackBase + stackSize, GetTlsSlot(), constant::DefaultPriority, this).item;
|
||||
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize, std::shared_ptr<type::KSharedMemory>& tlsMemory) : pid(pid), mainThreadStackSz(stackSize), KSyncObject(state, KType::KProcess) {
|
||||
auto thread = NewHandle<KThread>(pid, entryPoint, 0x0, stackBase + stackSize, 0, constant::DefaultPriority, this, tlsMemory).item;
|
||||
// Remove GetTlsSlot from KThread ctor and cleanup ctor in general
|
||||
threadMap[pid] = thread;
|
||||
state.nce->WaitThreadInit(thread);
|
||||
thread->tls = GetTlsSlot();
|
||||
MapPrivateRegion(constant::HeapAddr, constant::DefHeapSize, {true, true, false}, memory::Type::Heap, memory::Region::Heap);
|
||||
memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
|
||||
if (memFd == -1)
|
||||
@ -65,16 +69,23 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
std::shared_ptr<KThread> KProcess::CreateThread(u64 entryPoint, u64 entryArg, u64 stackTop, u8 priority) {
|
||||
user_pt_regs fregs = {0};
|
||||
/*
|
||||
* Future Reference:
|
||||
* https://android.googlesource.com/platform/bionic/+/master/libc/bionic/clone.cpp
|
||||
* https://android.googlesource.com/platform/bionic/+/master/libc/arch-arm64/bionic/__bionic_clone.S
|
||||
Registers fregs{};
|
||||
fregs.regs[0] = entryPoint;
|
||||
fregs.regs[1] = stackTop;
|
||||
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, pid);
|
||||
fregs.x8 = __NR_clone;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
|
||||
auto pid = static_cast<pid_t>(fregs.regs[0]);
|
||||
if (pid == -1)
|
||||
throw exception("Cannot create thread: Address: 0x{:X}, Stack Top: 0x{:X}", entryPoint, stackTop);
|
||||
auto process = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this).item;
|
||||
threadMap[pid] = process;
|
||||
return process;
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
|
||||
@ -90,7 +101,7 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
KProcess::HandleOut<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region) {
|
||||
auto mem = NewHandle<KPrivateMemory>(address, 0, size, perms, type, pid);
|
||||
auto mem = NewHandle<KPrivateMemory>(address, size, perms, type, threadMap.at(pid));
|
||||
memoryMap[mem.item->address] = mem.item;
|
||||
memoryRegionMap[region] = mem.item;
|
||||
return mem;
|
||||
|
@ -91,8 +91,9 @@ namespace skyline::kernel::type {
|
||||
* @param entryPoint The address to start execution at
|
||||
* @param stackBase The base of the stack
|
||||
* @param stackSize The size of the stack
|
||||
* @param tlsMemory The KSharedMemory object for TLS memory allocated by the guest process
|
||||
*/
|
||||
KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize);
|
||||
KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize, std::shared_ptr<type::KSharedMemory>& tlsMemory);
|
||||
|
||||
/**
|
||||
* Close the file descriptor to the process's memory
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <nce.h>
|
||||
#include <android/sharedmem.h>
|
||||
#include <unistd.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
u64 MapSharedFunc(u64 address, size_t size, u64 perms, u64 fd) {
|
||||
@ -13,23 +14,26 @@ namespace skyline::kernel::type {
|
||||
fd = ASharedMemory_create("", size);
|
||||
if (fd < 0)
|
||||
throw exception("An error occurred while creating shared memory: {}", fd);
|
||||
address = MapSharedFunc(address, size, static_cast<u64>(permission.Get()), static_cast<u64>(fd));
|
||||
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0));
|
||||
if (address == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise)
|
||||
throw exception("An occurred while mapping shared region: {}", strerror(errno));
|
||||
kernel = {address, size, permission};
|
||||
}
|
||||
|
||||
u64 KSharedMemory::Map(u64 address, u64 size, memory::Permission permission) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64>(permission.Get());
|
||||
fregs.regs[3] = static_cast<u64>(fd);
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapSharedFunc), fregs, state.process->pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x2 = static_cast<u64>(permission.Get());
|
||||
fregs.x3 = static_cast<u64>(MAP_SHARED | ((address) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
fregs.x4 = static_cast<u64>(fd);
|
||||
fregs.x4 = static_cast<u64>(-1);
|
||||
fregs.x8 = __NR_mmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while mapping shared region in child process");
|
||||
guest = {fregs.regs[0], size, permission};
|
||||
return fregs.regs[0];
|
||||
guest = {fregs.x0, size, permission};
|
||||
return fregs.x0;
|
||||
}
|
||||
|
||||
u64 UnmapSharedFunc(u64 address, size_t size) {
|
||||
@ -39,15 +43,15 @@ namespace skyline::kernel::type {
|
||||
KSharedMemory::~KSharedMemory() {
|
||||
try {
|
||||
if (guest.valid()) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = guest.address;
|
||||
fregs.regs[1] = guest.size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, state.process->pid);
|
||||
Registers fregs{};
|
||||
fregs.x0 = guest.address;
|
||||
fregs.x1 = guest.size;
|
||||
fregs.x8 = __NR_munmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
|
||||
}
|
||||
if (kernel.valid())
|
||||
UnmapSharedFunc(kernel.address, kernel.size);
|
||||
} catch (const std::exception &) {
|
||||
}
|
||||
} catch (const std::exception &) {}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
@ -57,17 +61,17 @@ namespace skyline::kernel::type {
|
||||
|
||||
void KSharedMemory::Resize(size_t size) {
|
||||
if (guest.valid()) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = guest.address;
|
||||
fregs.regs[1] = guest.size;
|
||||
fregs.regs[2] = size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapSharedFunc), fregs, state.process->pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
Registers fregs{};
|
||||
fregs.x0 = guest.address;
|
||||
fregs.x1 = guest.size;
|
||||
fregs.x2 = size;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while remapping shared region in child process");
|
||||
guest.size = size;
|
||||
}
|
||||
if (kernel.valid()) {
|
||||
if (RemapSharedFunc(kernel.address, kernel.size, size) == reinterpret_cast<u64>(MAP_FAILED))
|
||||
if (mremap(reinterpret_cast<void *>(kernel.address), kernel.size, size, 0) == MAP_FAILED)
|
||||
throw exception("An occurred while remapping shared region: {}", strerror(errno));
|
||||
kernel.size = size;
|
||||
}
|
||||
@ -79,17 +83,17 @@ namespace skyline::kernel::type {
|
||||
|
||||
void KSharedMemory::UpdatePermission(memory::Permission permission, bool host) {
|
||||
if (guest.valid() && !host) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = guest.address;
|
||||
fregs.regs[1] = guest.size;
|
||||
fregs.regs[2] = static_cast<u64>(guest.permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionSharedFunc), fregs, state.process->pid);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
Registers fregs{};
|
||||
fregs.x0 = guest.address;
|
||||
fregs.x1 = guest.size;
|
||||
fregs.x2 = static_cast<u64>(guest.permission.Get());
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while updating shared region's permissions in child process");
|
||||
guest.permission = permission;
|
||||
}
|
||||
if (kernel.valid() && host) {
|
||||
if (UpdatePermissionSharedFunc(kernel.address, kernel.size, static_cast<u64>(permission.Get())) == reinterpret_cast<u64>(MAP_FAILED))
|
||||
if (mprotect(reinterpret_cast<void *>(kernel.address), kernel.size, permission.Get()) == reinterpret_cast<u64>(MAP_FAILED))
|
||||
throw exception("An occurred while remapping shared region: {}", strerror(errno));
|
||||
kernel.permission = permission;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace skyline::kernel::type {
|
||||
|
||||
void KSyncObject::Signal() {
|
||||
for (const auto &info : waitThreads) {
|
||||
state.nce->SetRegister(Wreg::W1, info.index);
|
||||
state.ctx->registers.w1 = info.index;
|
||||
state.process->threadMap.at(info.process)->status = KThread::Status::Runnable;
|
||||
}
|
||||
waitThreads.clear();
|
||||
|
@ -4,7 +4,8 @@
|
||||
#include <nce.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KThread::KThread(const DeviceState &state, handle_t handle, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent) : handle(handle), pid(self_pid), entryPoint(entryPoint), entryArg(entryArg), stackTop(stackTop), tls(tls), priority(priority), parent(parent), KSyncObject(state, KType::KThread) {
|
||||
KThread::KThread(const DeviceState &state, handle_t handle, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent, std::shared_ptr<type::KSharedMemory>& tlsMemory) : handle(handle), pid(self_pid), entryPoint(entryPoint), entryArg(entryArg), stackTop(stackTop), tls(tls), priority(priority), parent(parent), ctxMemory(tlsMemory), KSyncObject(state,
|
||||
KType::KThread) {
|
||||
UpdatePriority(priority);
|
||||
}
|
||||
|
||||
@ -17,7 +18,7 @@ namespace skyline::kernel::type {
|
||||
if (pid == parent->pid)
|
||||
parent->status = KProcess::Status::Started;
|
||||
status = Status::Running;
|
||||
state.nce->StartProcess(entryPoint, entryArg, stackTop, handle, pid);
|
||||
state.nce->StartThread(entryArg, handle, parent->threadMap.at(pid));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "KSyncObject.h"
|
||||
#include "KSharedMemory.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
@ -23,6 +24,7 @@ namespace skyline::kernel::type {
|
||||
Runnable, //!< The thread is ready to run after waiting
|
||||
Dead //!< The thread is dead and not running
|
||||
} status = Status::Created; //!< The state of the thread
|
||||
std::shared_ptr<type::KSharedMemory> ctxMemory; //!< The KSharedMemory of the shared memory allocated by the guest process TLS
|
||||
std::vector<std::shared_ptr<KSyncObject>> waitObjects; //!< A vector holding the objects this thread is waiting for
|
||||
u64 timeout{}; //!< The end of a timeout for svcWaitSynchronization or the end of the sleep period for svcSleepThread
|
||||
handle_t handle; // The handle of the object in the handle table
|
||||
@ -41,8 +43,9 @@ namespace skyline::kernel::type {
|
||||
* @param tls The address of the TLS slot assigned
|
||||
* @param priority The priority of the thread in Nintendo format
|
||||
* @param parent The parent process of this thread
|
||||
* @param tlsMemory The KSharedMemory object for TLS memory allocated by the guest process
|
||||
*/
|
||||
KThread(const DeviceState &state, handle_t handle, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent);
|
||||
KThread(const DeviceState &state, handle_t handle, pid_t self_pid, u64 entryPoint, u64 entryArg, u64 stackTop, u64 tls, u8 priority, KProcess *parent, std::shared_ptr<type::KSharedMemory>& tlsMemory);
|
||||
|
||||
/**
|
||||
* @brief Kills the thread and deallocates the memory allocated for stack.
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "KTransferMemory.h"
|
||||
#include <nce.h>
|
||||
#include <os.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
u64 MapTransferFunc(u64 address, size_t size, u64 perms) {
|
||||
@ -9,14 +10,17 @@ namespace skyline::kernel::type {
|
||||
|
||||
KTransferMemory::KTransferMemory(const DeviceState &state, pid_t pid, u64 address, size_t size, const memory::Permission permission) : owner(pid), cSize(size), permission(permission), KObject(state, KType::KTransferMemory) {
|
||||
if (pid) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64 >(permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapTransferFunc), fregs, pid);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x2 = static_cast<u64 >(permission.Get());
|
||||
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
fregs.x4 = static_cast<u64>(-1);
|
||||
fregs.x8 = __NR_mmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, pid);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while mapping shared region in child process");
|
||||
cAddress = fregs.regs[0];
|
||||
cAddress = fregs.x0;
|
||||
} else {
|
||||
address = MapTransferFunc(address, size, static_cast<u64>(permission.Get()));
|
||||
if (reinterpret_cast<void *>(address) == MAP_FAILED)
|
||||
@ -31,14 +35,18 @@ namespace skyline::kernel::type {
|
||||
|
||||
u64 KTransferMemory::Transfer(pid_t process, u64 address, u64 size) {
|
||||
if (process) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64 >(permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapTransferFunc), fregs, process);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x2 = static_cast<u64 >(permission.Get());
|
||||
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
fregs.x4 = static_cast<u64>(-1);
|
||||
fregs.x8 = __NR_mmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, process);
|
||||
// state.nce->ExecuteFunction(reinterpret_cast<void *>(MapTransferFunc), fregs, process);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while mapping transfer memory in child process");
|
||||
address = fregs.regs[0];
|
||||
address = fregs.x0;
|
||||
} else {
|
||||
address = MapTransferFunc(address, size, static_cast<u64>(permission.Get()));
|
||||
if (reinterpret_cast<void *>(address) == MAP_FAILED)
|
||||
@ -52,12 +60,13 @@ namespace skyline::kernel::type {
|
||||
} else
|
||||
throw exception("Transferring from kernel to kernel is not supported");
|
||||
if (owner) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<u64 >(permission.Get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapTransferFunc), fregs, owner);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
fregs.x8 = __NR_munmap;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, owner);
|
||||
// state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapTransferFunc), fregs, owner);
|
||||
if (fregs.x0 < 0)
|
||||
throw exception("An error occurred while unmapping transfer memory in child process");
|
||||
} else {
|
||||
if (reinterpret_cast<void *>(UnmapTransferFunc(address, size)) == MAP_FAILED)
|
||||
@ -87,12 +96,11 @@ namespace skyline::kernel::type {
|
||||
KTransferMemory::~KTransferMemory() {
|
||||
if (owner) {
|
||||
try {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = cAddress;
|
||||
fregs.regs[1] = cSize;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapTransferFunc), fregs, owner);
|
||||
} catch (const std::exception &) {
|
||||
}
|
||||
Registers fregs{};
|
||||
fregs.x0 = cAddress;
|
||||
fregs.x1 = cSize;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, owner);
|
||||
} catch (const std::exception &) {}
|
||||
} else
|
||||
UnmapTransferFunc(cAddress, cSize);
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ namespace skyline::loader {
|
||||
}
|
||||
|
||||
public:
|
||||
u64 mainEntry{}; //!< The address of the actual entry point for the application
|
||||
|
||||
/**
|
||||
* @param filePath The path to the ROM file
|
||||
*/
|
||||
|
@ -6,6 +6,7 @@ namespace skyline::loader {
|
||||
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
|
||||
if (header.magic != constant::NroMagic)
|
||||
throw exception("Invalid NRO magic! 0x{0:X}", header.magic);
|
||||
mainEntry = constant::BaseAddr;
|
||||
}
|
||||
|
||||
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
|
||||
@ -17,7 +18,7 @@ namespace skyline::loader {
|
||||
ReadOffset(rodata.data(), header.ro.offset, header.ro.size);
|
||||
ReadOffset(data.data(), header.data.offset, header.data.size);
|
||||
|
||||
std::vector<u32> patch = state.nce->PatchCode(text, header.text.size + header.ro.size + header.data.size + header.bssSize);
|
||||
std::vector<u32> patch = state.nce->PatchCode(text, constant::BaseAddr, header.text.size + header.ro.size + header.data.size + header.bssSize);
|
||||
|
||||
u64 textSize = text.size();
|
||||
u64 rodataSize = rodata.size();
|
||||
|
@ -1,237 +1,184 @@
|
||||
#include <sched.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/elf.h>
|
||||
#include <mutex>
|
||||
#include "os.h"
|
||||
#include "jvm.h"
|
||||
#include "guest.h"
|
||||
#include "nce/guest.h"
|
||||
#include "nce/instr.h"
|
||||
#include "kernel/svc.h"
|
||||
|
||||
extern bool Halt;
|
||||
extern skyline::Mutex jniMtx;
|
||||
|
||||
namespace skyline {
|
||||
void NCE::ReadRegisters(user_pt_regs ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, sizeof(registers)};
|
||||
long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
|
||||
if (status == -1)
|
||||
throw exception("Cannot read registers, PID: {}, Error: {}", pid, strerror(errno));
|
||||
void NCE::KernelThread(pid_t thread) {
|
||||
state.thread = state.process->threadMap.at(thread);
|
||||
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->guest.address);
|
||||
while (!Halt) {
|
||||
if (state.ctx->state == ThreadState::WaitKernel) {
|
||||
const u16 svc = static_cast<const u16>(state.ctx->commandId);
|
||||
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 exception &e) {
|
||||
throw exception("{} (SVC: 0x{:X})", e.what(), svc);
|
||||
}
|
||||
state.ctx->state = ThreadState::WaitRun;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::WriteRegisters(user_pt_regs ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, sizeof(registers)};
|
||||
long status = ptrace(PTRACE_SETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
|
||||
if (status == -1)
|
||||
throw exception("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno));
|
||||
}
|
||||
|
||||
instr::Brk NCE::ReadBrk(u64 address, pid_t pid) const {
|
||||
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : currPid, address, NULL);
|
||||
if (status == -1)
|
||||
throw exception("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno));
|
||||
return *(reinterpret_cast<instr::Brk *>(&status));
|
||||
}
|
||||
|
||||
NCE::NCE(const DeviceState &state) : state(state) {}
|
||||
NCE::NCE(DeviceState &state) : state(state) {}
|
||||
|
||||
void NCE::Execute() {
|
||||
int status = 0;
|
||||
while (!Halt && state.os->process) {
|
||||
std::lock_guard jniGd(jniMtx);
|
||||
for (const auto &thread : state.os->process->threadMap) {
|
||||
state.os->thisThread = thread.second;
|
||||
currPid = thread.first;
|
||||
auto &currRegs = registerMap[currPid];
|
||||
if (state.thread->status == kernel::type::KThread::Status::Running) {
|
||||
if (waitpid(state.thread->pid, &status, WNOHANG) == state.thread->pid) {
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
ReadRegisters(currRegs);
|
||||
auto instr = ReadBrk(currRegs.pc);
|
||||
if (instr.Verify()) {
|
||||
// We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
|
||||
if (instr.value <= constant::SvcLast) {
|
||||
state.os->SvcHandler(static_cast<u16>(instr.value));
|
||||
if (state.thread->status != kernel::type::KThread::Status::Running)
|
||||
continue;
|
||||
} else if (instr.value > constant::SvcLast && instr.value <= constant::SvcLast + constant::NumRegs) {
|
||||
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
|
||||
SetRegister(static_cast<Xreg>(instr.value - (constant::SvcLast + 1)), state.thread->tls);
|
||||
} else if (instr.value == constant::BrkRdy)
|
||||
continue;
|
||||
else {
|
||||
ProcessTrace();
|
||||
throw exception("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value));
|
||||
}
|
||||
}
|
||||
currRegs.pc += sizeof(u32);
|
||||
WriteRegisters(currRegs);
|
||||
ResumeProcess();
|
||||
} else {
|
||||
try {
|
||||
state.logger->Warn("Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
|
||||
ProcessTrace();
|
||||
} catch (const exception &) {
|
||||
state.logger->Warn("Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
state.os->KillThread(currPid);
|
||||
}
|
||||
}
|
||||
} else if ((state.thread->status == kernel::type::KThread::Status::WaitSync || state.thread->status == kernel::type::KThread::Status::Sleeping || state.thread->status == kernel::type::KThread::Status::WaitCondVar) && state.thread->timeout != 0) { // timeout == 0 means sleep forever
|
||||
if (state.thread->timeout <= GetCurrTimeNs()) {
|
||||
if(state.thread->status != kernel::type::KThread::Status::Sleeping)
|
||||
state.logger->Info("An event has timed out: {}", state.thread->status);
|
||||
if (state.thread->status == kernel::type::KThread::Status::WaitSync || state.thread->status == kernel::type::KThread::Status::WaitCondVar)
|
||||
SetRegister(Wreg::W0, constant::status::Timeout);
|
||||
state.thread->status = kernel::type::KThread::Status::Runnable;
|
||||
}
|
||||
}
|
||||
if (state.thread->status == kernel::type::KThread::Status::Runnable) {
|
||||
state.thread->ClearWaitObjects();
|
||||
state.thread->status = kernel::type::KThread::Status::Running;
|
||||
currRegs.pc += sizeof(u32);
|
||||
WriteRegisters(currRegs);
|
||||
ResumeProcess();
|
||||
}
|
||||
}
|
||||
state.os->serviceManager.Loop();
|
||||
state.gpu->Loop();
|
||||
}
|
||||
Halt = false;
|
||||
}
|
||||
|
||||
void BrkLr() {
|
||||
asm("BRK #0xFF"); // BRK #constant::brkRdy
|
||||
void NCE::ExecuteFunction(ThreadCall call, Registers &funcRegs, std::shared_ptr<kernel::type::KThread>& thread) {
|
||||
auto ctx = reinterpret_cast<ThreadContext *>(thread->ctxMemory->kernel.address);
|
||||
u32 cmdId = ctx->commandId;
|
||||
Registers registers = ctx->registers;
|
||||
while(ctx->state != ThreadState::WaitInit && ctx->state != ThreadState::WaitKernel);
|
||||
ctx->registers = funcRegs;
|
||||
ctx->commandId = static_cast<u32>(call);
|
||||
ctx->state = ThreadState::WaitFunc;
|
||||
while(ctx->state != ThreadState::WaitInit && ctx->state != ThreadState::WaitKernel);
|
||||
ctx->commandId = cmdId;
|
||||
funcRegs = ctx->registers;
|
||||
ctx->registers = registers;
|
||||
}
|
||||
|
||||
void NCE::ExecuteFunction(void *func, user_pt_regs &funcRegs, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
bool wasRunning = PauseProcess(pid);
|
||||
user_pt_regs backupRegs{};
|
||||
ReadRegisters(backupRegs, pid);
|
||||
funcRegs.pc = reinterpret_cast<u64>(func);
|
||||
funcRegs.sp = backupRegs.sp;
|
||||
funcRegs.regs[static_cast<uint>(Xreg::X30)] = reinterpret_cast<u64>(BrkLr); // Set LR to 'brk_lr' so the application will hit a breakpoint after the function returns
|
||||
WriteRegisters(funcRegs, pid);
|
||||
ResumeProcess(pid);
|
||||
funcRegs = WaitRdy(pid);
|
||||
WriteRegisters(backupRegs, pid);
|
||||
if (wasRunning)
|
||||
ResumeProcess(pid);
|
||||
void NCE::ExecuteFunction(ThreadCall call, Registers &funcRegs, pid_t pid) {
|
||||
auto ctx = reinterpret_cast<ThreadContext *>(state.process->threadMap.at(pid)->ctxMemory->kernel.address);
|
||||
ctx->commandId = static_cast<u32>(call);
|
||||
Registers registers = ctx->registers;
|
||||
while(ctx->state != ThreadState::WaitInit || ctx->state != ThreadState::WaitKernel);
|
||||
ctx->registers = funcRegs;
|
||||
ctx->state = ThreadState::WaitFunc;
|
||||
while(ctx->state != ThreadState::WaitInit || ctx->state != ThreadState::WaitKernel);
|
||||
funcRegs = ctx->registers;
|
||||
ctx->registers = registers;
|
||||
}
|
||||
|
||||
user_pt_regs NCE::WaitRdy(pid_t pid) {
|
||||
int status;
|
||||
user_pt_regs regs{};
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
ReadRegisters(regs, pid);
|
||||
auto instr = ReadBrk(regs.pc, pid);
|
||||
if (instr.Verify() && instr.value == constant::BrkRdy) {
|
||||
regs.pc += 4; // Increment program counter by a single instruction (32 bits)
|
||||
WriteRegisters(regs, pid);
|
||||
return regs;
|
||||
} else
|
||||
throw exception("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value));
|
||||
} else
|
||||
throw exception("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
|
||||
void NCE::WaitThreadInit(std::shared_ptr<kernel::type::KThread>& thread) {
|
||||
auto ctx = reinterpret_cast<ThreadContext *>(thread->ctxMemory->kernel.address);
|
||||
while(ctx->state == ThreadState::NotReady);
|
||||
}
|
||||
|
||||
bool NCE::PauseProcess(pid_t pid) const {
|
||||
pid = pid ? pid : currPid;
|
||||
int status = 0;
|
||||
waitpid(pid, &status, WNOHANG);
|
||||
bool wasStopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise)
|
||||
if (wasStopped) {
|
||||
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, WNOHANG) != -1))
|
||||
return true;
|
||||
else
|
||||
throw exception("Cannot pause process: {}, Error: {}", pid, strerror(errno));
|
||||
} else
|
||||
return false;
|
||||
void NCE::StartThread(u64 entryArg, u32 handle, std::shared_ptr<kernel::type::KThread> &thread) {
|
||||
auto ctx = reinterpret_cast<ThreadContext *>(thread->ctxMemory->kernel.address);
|
||||
while(ctx->state != ThreadState::WaitInit);
|
||||
ctx->tpidrroEl0 = thread->tls;
|
||||
ctx->registers.x0 = entryArg;
|
||||
ctx->registers.x1 = handle;
|
||||
ctx->state = ThreadState::WaitRun;
|
||||
while(ctx->state != ThreadState::Running);
|
||||
threadMap[thread->pid] = std::make_shared<std::thread>(&NCE::KernelThread, this, thread->pid);
|
||||
}
|
||||
|
||||
void NCE::ResumeProcess(pid_t pid) const {
|
||||
long status = ptrace(PTRACE_CONT, pid ? pid : currPid, NULL, NULL);
|
||||
if (status == -1)
|
||||
throw exception("Cannot resume process: {}, Error: {}", pid, strerror(errno));
|
||||
}
|
||||
|
||||
void NCE::StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const {
|
||||
user_pt_regs regs{0};
|
||||
regs.sp = stackTop;
|
||||
regs.pc = entryPoint;
|
||||
regs.regs[0] = entryArg;
|
||||
regs.regs[1] = handle;
|
||||
WriteRegisters(regs, pid);
|
||||
ResumeProcess(pid);
|
||||
}
|
||||
|
||||
void NCE::ProcessTrace(u16 numHist, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
user_pt_regs regs{};
|
||||
ReadRegisters(regs, pid);
|
||||
u64 offset = regs.pc - (sizeof(u32) * numHist);
|
||||
std::string raw{};
|
||||
state.logger->Debug("Process Trace:");
|
||||
for (; offset <= (regs.pc + sizeof(u32)); offset += sizeof(u32)) {
|
||||
u32 instr = __builtin_bswap32(static_cast<u32>(ptrace(PTRACE_PEEKDATA, pid, offset, NULL)));
|
||||
if (offset == regs.pc)
|
||||
state.logger->Debug("-> 0x{:X} : 0x{:08X}", offset, instr);
|
||||
else
|
||||
state.logger->Debug(" 0x{:X} : 0x{:08X}", offset, instr);
|
||||
raw += fmt::format("{:08X}", instr);
|
||||
}
|
||||
state.logger->Debug("Raw Instructions: 0x{}", raw);
|
||||
void NCE::ThreadTrace(u16 numHist, ThreadContext *ctx) {
|
||||
std::string raw;
|
||||
std::string trace;
|
||||
std::string regStr;
|
||||
ctx = ctx ? ctx : state.ctx;
|
||||
if(numHist) {
|
||||
std::vector<u32> instrs(numHist);
|
||||
u64 size = (sizeof(u32) * numHist);
|
||||
u64 offset = ctx->pc - size;
|
||||
state.process->ReadMemory(instrs.data(), offset, size);
|
||||
for (auto &instr : instrs) {
|
||||
instr = __builtin_bswap32(instr);
|
||||
if (offset == ctx->pc)
|
||||
trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instr);
|
||||
else
|
||||
trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instr);
|
||||
raw += fmt::format("{:08X}", instr);
|
||||
offset += sizeof(u32);
|
||||
}
|
||||
}
|
||||
for (u16 index = 0; index < constant::NumRegs - 1; index+=2) {
|
||||
regStr += fmt::format("\nX{}: 0x{:X}, X{}: 0x{:X}", index, regs.regs[index], index+1, regs.regs[index+1]);
|
||||
regStr += fmt::format("\nX{}: 0x{:X}, X{}: 0x{:X}", index, ctx->registers.regs[index], index+1, ctx->registers.regs[index+1]);
|
||||
}
|
||||
state.logger->Debug("CPU Context:\nSP: 0x{:X}\nLR: 0x{:X}\nPSTATE: 0x{:X}{}", regs.sp, regs.regs[30], regs.pstate, regStr);
|
||||
if(numHist)
|
||||
state.logger->Debug("Process Trace:{}\nRaw Instructions: 0x{}\nCPU Context:{}", trace, raw, regStr);
|
||||
else
|
||||
state.logger->Warn("CPU Context:{}", regStr);
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(Xreg regId, pid_t pid) {
|
||||
return registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)];
|
||||
}
|
||||
const std::array<u32, 18> cntpctEl0X0 = {
|
||||
0xA9BF0BE1, // STP X1, X2, [SP, #-16]!
|
||||
0x3C9F0FE0, // STR Q0, [SP, #-16]!
|
||||
0x3C9F0FE1, // STR Q1, [SP, #-16]!
|
||||
0x3C9F0FE2, // STR Q2, [SP, #-16]!
|
||||
0xD53BE001, // MRS X1, CNTFRQ_EL0
|
||||
0xD53BE042, // MRS X2, CNTVCT_EL0
|
||||
0x9E630020, // UCVTF D0, X0
|
||||
0xD2C9F001, // MOV X1, 87411174408192
|
||||
0xF2E82E41, // MOVK X1, 0x4172, LSL 48
|
||||
0x9E670022, // FMOV D2, X1
|
||||
0x9E630041, // UCVTF D1, X1
|
||||
0x1E621800, // FDIV D0, D0, D2
|
||||
0x1E610800, // FMUL D0, D0, D1
|
||||
0x9E790000, // FCVTZU X0, D0
|
||||
0x3CC107E2, // LDR Q2, [SP], #16
|
||||
0x3CC107E1, // LDR Q1, [SP], #16
|
||||
0xA97F07C0, // LDP X1, X2, [LR, #-16]
|
||||
};
|
||||
|
||||
void NCE::SetRegister(Xreg regId, u64 value, pid_t pid) {
|
||||
registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)] = value;
|
||||
}
|
||||
const std::array<u32, 18> cntpctEl0X1 = {
|
||||
0xA9BF0BE0, // STP X0, X2, [SP, #-16]!
|
||||
0x3C9F0FE0, // STR Q0, [SP, #-16]!
|
||||
0x3C9F0FE1, // STR Q1, [SP, #-16]!
|
||||
0x3C9F0FE2, // STR Q2, [SP, #-16]!
|
||||
0xD53BE000, // MRS X0, CNTFRQ_EL0
|
||||
0xD53BE042, // MRS X2, CNTVCT_EL0
|
||||
0x9E630020, // UCVTF D0, X0
|
||||
0xD2C9F000, // MOV X0, 87411174408192
|
||||
0xF2E82E40, // MOVK X0, 0x4172, LSL 48
|
||||
0x9E670002, // FMOV D2, X0
|
||||
0x9E630041, // UCVTF D1, X2
|
||||
0x1E621800, // FDIV D0, D0, D2
|
||||
0x1E610800, // FMUL D0, D0, D1
|
||||
0x9E790001, // FCVTZU X0, D0
|
||||
0x3CC107E2, // LDR Q2, [SP], #16
|
||||
0x3CC107E1, // LDR Q1, [SP], #16
|
||||
0xA97F0BC0, // LDP X0, X2, [LR, #-16]
|
||||
};
|
||||
|
||||
u32 NCE::GetRegister(Wreg regId, pid_t pid) {
|
||||
return (reinterpret_cast<u32 *>(®isterMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2];
|
||||
}
|
||||
const std::array<u32, 18> cntpctEl0Xn = {
|
||||
0xA9BF07E0, // STP X0, X1, [SP, #-16]!
|
||||
0x3C9F0FE0, // STR Q0, [SP, #-16]!
|
||||
0x3C9F0FE1, // STR Q1, [SP, #-16]!
|
||||
0x3C9F0FE2, // STR Q2, [SP, #-16]!
|
||||
0xD53BE000, // MRS X0, CNTFRQ_EL0
|
||||
0xD53BE041, // MRS X1, CNTVCT_EL0
|
||||
0x9E630000, // UCVTF D0, X0
|
||||
0xD2C9F000, // MOV X0, 87411174408192
|
||||
0xF2E82E40, // MOVK X0, 0x4172, LSL 48
|
||||
0x9E670002, // FMOV D2, X0
|
||||
0x9E630021, // UCVTF D1, X1
|
||||
0x1E621800, // FDIV D0, D0, D2
|
||||
0x1E610800, // FMUL D0, D0, D1
|
||||
0x9E790000, // FCVTZU Xn, D0
|
||||
0x3CC107E2, // LDR Q2, [SP], #16
|
||||
0x3CC107E1, // LDR Q1, [SP], #16
|
||||
0xA97F07C0, // LDP X0, X1, [LR, #-16]
|
||||
};
|
||||
|
||||
void NCE::SetRegister(Wreg regId, u32 value, pid_t pid) {
|
||||
(reinterpret_cast<u32 *>(®isterMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2] = value;
|
||||
}
|
||||
|
||||
u64 NCE::GetRegister(Sreg regId, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
switch (regId) {
|
||||
case Sreg::Pc:
|
||||
return registerMap.at(pid).pc;
|
||||
case Sreg::Sp:
|
||||
return registerMap.at(pid).sp;
|
||||
case Sreg::PState:
|
||||
return registerMap.at(pid).pstate;
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::SetRegister(Sreg regId, u32 value, pid_t pid) {
|
||||
pid = pid ? pid : currPid;
|
||||
switch (regId) {
|
||||
case Sreg::Pc:
|
||||
registerMap.at(pid).pc = value;
|
||||
case Sreg::Sp:
|
||||
registerMap.at(pid).sp = value;
|
||||
case Sreg::PState:
|
||||
registerMap.at(pid).pstate = value;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> NCE::PatchCode(std::vector<u8>& code, i64 offset) {
|
||||
u32 *address = reinterpret_cast<u32 *>(code.data());
|
||||
u32 *end = address + (code.size() / sizeof(u32));
|
||||
std::vector<u32> NCE::PatchCode(std::vector<u8> &code, u64 baseAddress, i64 offset) {
|
||||
u32 *start = reinterpret_cast<u32 *>(code.data());
|
||||
u32 *end = start + (code.size() / sizeof(u32));
|
||||
i64 patchOffset = offset;
|
||||
|
||||
std::vector<u32> patch((guest::saveCtxSize + guest::loadCtxSize) / sizeof(u32));
|
||||
std::vector<u32> patch((guest::saveCtxSize + guest::loadCtxSize + guest::svcHandlerSize) / sizeof(u32));
|
||||
|
||||
std::memcpy(patch.data(), reinterpret_cast<void*>(&guest::saveCtx), guest::saveCtxSize);
|
||||
offset += guest::saveCtxSize;
|
||||
|
||||
@ -239,18 +186,29 @@ namespace skyline {
|
||||
reinterpret_cast<void*>(&guest::loadCtx), guest::loadCtxSize);
|
||||
offset += guest::loadCtxSize;
|
||||
|
||||
while (address < end) {
|
||||
std::memcpy(reinterpret_cast<u8*>(patch.data()) + guest::saveCtxSize + guest::loadCtxSize,
|
||||
reinterpret_cast<void*>(&guest::svcHandler), guest::svcHandlerSize);
|
||||
offset += guest::svcHandlerSize;
|
||||
|
||||
for (u32* address = start;address < end;address++) {
|
||||
auto instrSvc = reinterpret_cast<instr::Svc *>(address);
|
||||
auto instrMrs = reinterpret_cast<instr::Mrs *>(address);
|
||||
|
||||
if (instrSvc->Verify()) {
|
||||
u64 pc = baseAddress + (address - start);
|
||||
instr::B bjunc(offset);
|
||||
constexpr u32 strLr = 0xF81F0FFE; // STR LR, [SP, #-16]!
|
||||
offset += sizeof(strLr);
|
||||
instr::Brk brk(static_cast<u16>(instrSvc->value));
|
||||
offset += sizeof(brk);
|
||||
instr::BL bSvCtx(patchOffset - offset);
|
||||
offset += sizeof(bSvCtx);
|
||||
|
||||
auto movPc = instr::MoveU64Reg(regs::X0, pc);
|
||||
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
|
||||
@ -260,28 +218,79 @@ namespace skyline {
|
||||
|
||||
*address = bjunc.raw;
|
||||
patch.push_back(strLr);
|
||||
patch.push_back(brk.raw);
|
||||
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 == constant::TpidrroEl0) {
|
||||
instr::B bjunc(offset);
|
||||
instr::Brk brk(static_cast<u16>(constant::SvcLast + 1 + instrMrs->dstReg));
|
||||
offset += sizeof(u32);
|
||||
u32 strX0{};
|
||||
if(instrMrs->destReg != regs::X0) {
|
||||
strX0 = 0xF81F0FE0; // STR X0, [SP, #-16]!
|
||||
offset += sizeof(strX0);
|
||||
}
|
||||
u32 mrsX0 = 0xD53BD040; // MRS X0, TPIDR_EL0
|
||||
offset += sizeof(mrsX0);
|
||||
u32 ldrTls = 0xF9408000; // LDR X0, [X0, #256]
|
||||
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(u32);
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bjunc.raw;
|
||||
patch.push_back(brk.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);
|
||||
} if (instrMrs->srcReg == constant::CntpctEl0) {
|
||||
instr::B bjunc(offset);
|
||||
if(instrMrs->destReg == 0)
|
||||
offset += cntpctEl0X0.size() * sizeof(u32);
|
||||
else if (instrMrs->destReg == 1)
|
||||
offset += cntpctEl0X1.size() * sizeof(u32);
|
||||
else
|
||||
offset += cntpctEl0Xn.size() * sizeof(u32);
|
||||
instr::B bret(-offset + sizeof(u32));
|
||||
offset += sizeof(bret);
|
||||
|
||||
*address = bjunc.raw;
|
||||
if(instrMrs->destReg == 0)
|
||||
for(auto& instr : cntpctEl0X0)
|
||||
patch.push_back(instr);
|
||||
else if (instrMrs->destReg == 1)
|
||||
for(auto& instr : cntpctEl0X1)
|
||||
patch.push_back(instr);
|
||||
else
|
||||
for(auto& instr : cntpctEl0Xn)
|
||||
patch.push_back(instr);
|
||||
patch.push_back(bret.raw);
|
||||
} else if (instrMrs->srcReg == constant::CntfrqEl0) {
|
||||
instr::B bjunc(offset);
|
||||
auto movFreq = instr::MoveU32Reg(static_cast<regs::X>(instrMrs->destReg), constant::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);
|
||||
} else if (instrMrs->srcReg == constant::CntpctEl0) {
|
||||
instr::Mrs mrs(constant::CntvctEl0, instrMrs->dstReg);
|
||||
*address = mrs.raw;
|
||||
}
|
||||
}
|
||||
address++;
|
||||
offset -= sizeof(u32);
|
||||
patchOffset -= sizeof(u32);
|
||||
}
|
||||
|
@ -9,146 +9,72 @@
|
||||
#include "kernel/types/KSharedMemory.h"
|
||||
|
||||
namespace skyline {
|
||||
namespace instr {
|
||||
struct Brk;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The NCE (Native Code Execution) class is responsible for managing the state of catching instructions and directly controlling processes/threads
|
||||
*/
|
||||
class NCE {
|
||||
private:
|
||||
pid_t currPid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly
|
||||
std::unordered_map<pid_t, user_pt_regs> registerMap; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
|
||||
const DeviceState &state; //!< The state of the device
|
||||
DeviceState &state; //!< The state of the device
|
||||
std::unordered_map<pid_t, std::shared_ptr<std::thread>> threadMap; //!< This maps all of the host threads to their corresponding kernel thread
|
||||
|
||||
/**
|
||||
* @brief Reads process registers into the `registers` variable
|
||||
* @param registers A set of registers to fill with values from the process
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @brief This function is the event loop of a kernel thread managing a guest thread
|
||||
* @param thread The PID of the thread to manage
|
||||
*/
|
||||
void ReadRegisters(user_pt_regs ®isters, pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Writes process registers from the `registers` variable
|
||||
* @param registers The registers to be written by the process
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void WriteRegisters(user_pt_regs ®isters, pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Reads a BRK instruction, this is used to get it's immediate value
|
||||
* @param address The address of the BRK instruction
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return An instance of BRK with the corresponding values
|
||||
*/
|
||||
instr::Brk ReadBrk(u64 address, pid_t pid = 0) const;
|
||||
void KernelThread(pid_t thread);
|
||||
|
||||
public:
|
||||
NCE(const DeviceState &state);
|
||||
NCE(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Start the event loop of executing the program
|
||||
* @brief This function is the main event loop of the program
|
||||
*/
|
||||
void Execute();
|
||||
|
||||
/**
|
||||
* @brief Execute any arbitrary function on a particular child process
|
||||
* @param func The entry point of the function
|
||||
* @param funcRegs A set of registers to run the function with (PC, SP and X29 are replaced)
|
||||
* @param call The specific call to execute
|
||||
* @param funcRegs A set of registers to run the function
|
||||
* @param thread The thread to execute the function on
|
||||
*/
|
||||
void ExecuteFunction(ThreadCall call, Registers &funcRegs, std::shared_ptr<kernel::type::KThread>& thread);
|
||||
|
||||
/**
|
||||
* @brief Execute any arbitrary function on a particular child process
|
||||
* @param call The specific call to execute
|
||||
* @param funcRegs A set of registers to run the function
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void ExecuteFunction(void *func, user_pt_regs &funcRegs, pid_t pid);
|
||||
void ExecuteFunction(ThreadCall call, Registers &funcRegs, pid_t pid);
|
||||
|
||||
/**
|
||||
* @brief Waits till a process calls "BRK #constant::brk_rdy"
|
||||
* @param pid The PID of the process
|
||||
* @return The registers after the BRK
|
||||
* @brief Waits till a thread is ready to execute commands
|
||||
* @param thread The KThread to wait for initialization
|
||||
*/
|
||||
user_pt_regs WaitRdy(pid_t pid);
|
||||
void WaitThreadInit(std::shared_ptr<kernel::type::KThread>& thread);
|
||||
|
||||
/**
|
||||
* @brief Pauses a particular process if was not already paused
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return If the application was paused beforehand
|
||||
*/
|
||||
bool PauseProcess(pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Resumes a particular process, does nothing if it was already running
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void ResumeProcess(pid_t pid = 0) const;
|
||||
|
||||
/**
|
||||
* @brief Starts a particular process, sets the registers to their expected values and jumps to address
|
||||
* @param entryPoint The address to jump to
|
||||
* @brief Sets the X0 and X1 registers in a thread and starts it and it's kernel thread
|
||||
* @param entryArg The argument to pass in for the entry function
|
||||
* @param stackTop The top of the stack
|
||||
* @param handle The handle of the main thread (Set to value of 1st register)
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @param handle The handle of the main thread
|
||||
* @param thread The thread to set the registers and start
|
||||
* @note This function will block forever if the thread has already started
|
||||
*/
|
||||
void StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const;
|
||||
void StartThread(u64 entryArg, u32 handle, std::shared_ptr<kernel::type::KThread> &thread);
|
||||
|
||||
/**
|
||||
* @brief This prints out a trace and the CPU context
|
||||
* @param numHist The amount of previous instructions to print
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @param numHist The amount of previous instructions to print (Can be 0)
|
||||
* @param ctx The ThreadContext of the thread to log
|
||||
*/
|
||||
void ProcessTrace(u16 numHist = 10, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a Xn register
|
||||
* @param regId The ID of the register
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return The value of the register
|
||||
*/
|
||||
u64 GetRegister(Xreg regId, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a Xn register
|
||||
* @param regId The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void SetRegister(Xreg regId, u64 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a Wn register
|
||||
* @param regId The ID of the register
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return The value in the register
|
||||
*/
|
||||
u32 GetRegister(Wreg regId, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a Wn register
|
||||
* @param regId The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void SetRegister(Wreg regId, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a special register
|
||||
* @param regId The ID of the register
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
* @return The value in the register
|
||||
*/
|
||||
u64 GetRegister(Sreg regId, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a special register
|
||||
* @param regId The ID of the register
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process (Defaults to currPid)
|
||||
*/
|
||||
void SetRegister(Sreg regId, u32 value, pid_t pid = 0);
|
||||
void ThreadTrace(u16 numHist = 10, ThreadContext *ctx = nullptr);
|
||||
|
||||
/**
|
||||
* @brief This 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 offset The offset of the code block from the base address
|
||||
*/
|
||||
std::vector<u32> PatchCode(std::vector<u8> &code, i64 offset);
|
||||
std::vector<u32> PatchCode(std::vector<u8> &code, u64 baseAddress, i64 offset);
|
||||
};
|
||||
}
|
||||
|
44
app/src/main/cpp/skyline/nce/guest.S
Normal file
44
app/src/main/cpp/skyline/nce/guest.S
Normal file
@ -0,0 +1,44 @@
|
||||
.text
|
||||
.global saveCtx
|
||||
saveCtx:
|
||||
STR LR, [SP, #-16]!
|
||||
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
|
||||
RET
|
||||
|
||||
.global loadCtx
|
||||
loadCtx:
|
||||
STR LR, [SP, #-16]!
|
||||
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
|
||||
RET
|
162
app/src/main/cpp/skyline/nce/guest.cpp
Normal file
162
app/src/main/cpp/skyline/nce/guest.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
#include "guest_common.h"
|
||||
|
||||
#define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage)
|
||||
|
||||
namespace skyline::guest {
|
||||
FORCE_INLINE void saveCtxStack() {
|
||||
asm("SUB SP, SP, #240\n\t"
|
||||
"STP X0, X1, [SP, #0]\n\t"
|
||||
"STP X2, X3, [SP, #16]\n\t"
|
||||
"STP X4, X5, [SP, #32]\n\t"
|
||||
"STP X6, X7, [SP, #48]\n\t"
|
||||
"STP X8, X9, [SP, #64]\n\t"
|
||||
"STP X10, X11, [SP, #80]\n\t"
|
||||
"STP X12, X13, [SP, #96]\n\t"
|
||||
"STP X14, X15, [SP, #112]\n\t"
|
||||
"STP X16, X17, [SP, #128]\n\t"
|
||||
"STP X18, X19, [SP, #144]\n\t"
|
||||
"STP X20, X21, [SP, #160]\n\t"
|
||||
"STP X22, X23, [SP, #176]\n\t"
|
||||
"STP X24, X25, [SP, #192]\n\t"
|
||||
"STP X26, X27, [SP, #208]\n\t"
|
||||
"STP X28, X29, [SP, #224]"
|
||||
);
|
||||
}
|
||||
|
||||
FORCE_INLINE void loadCtxStack() {
|
||||
asm("LDP X0, X1, [SP, #0]\n\t"
|
||||
"LDP X2, X3, [SP, #16]\n\t"
|
||||
"LDP X4, X5, [SP, #32]\n\t"
|
||||
"LDP X6, X7, [SP, #48]\n\t"
|
||||
"LDP X8, X9, [SP, #64]\n\t"
|
||||
"LDP X10, X11, [SP, #80]\n\t"
|
||||
"LDP X12, X13, [SP, #96]\n\t"
|
||||
"LDP X14, X15, [SP, #112]\n\t"
|
||||
"LDP X16, X17, [SP, #128]\n\t"
|
||||
"LDP X18, X19, [SP, #144]\n\t"
|
||||
"LDP X20, X21, [SP, #160]\n\t"
|
||||
"LDP X22, X23, [SP, #176]\n\t"
|
||||
"LDP X24, X25, [SP, #192]\n\t"
|
||||
"LDP X26, X27, [SP, #208]\n\t"
|
||||
"LDP X28, X29, [SP, #224]\n\t"
|
||||
"ADD SP, SP, #240"
|
||||
);
|
||||
}
|
||||
|
||||
FORCE_INLINE void saveCtxTls() {
|
||||
asm("STR LR, [SP, #-16]!\n\t"
|
||||
"MRS LR, TPIDR_EL0\n\t"
|
||||
"STP X0, X1, [LR, #16]\n\t"
|
||||
"STP X2, X3, [LR, #32]\n\t"
|
||||
"STP X4, X5, [LR, #48]\n\t"
|
||||
"STP X6, X7, [LR, #64]\n\t"
|
||||
"STP X8, X9, [LR, #80]\n\t"
|
||||
"STP X10, X11, [LR, #96]\n\t"
|
||||
"STP X12, X13, [LR, #112]\n\t"
|
||||
"STP X14, X15, [LR, #128]\n\t"
|
||||
"STP X16, X17, [LR, #144]\n\t"
|
||||
"STP X18, X19, [LR, #160]\n\t"
|
||||
"STP X20, X21, [LR, #176]\n\t"
|
||||
"STP X22, X23, [LR, #192]\n\t"
|
||||
"STP X24, X25, [LR, #208]\n\t"
|
||||
"STP X26, X27, [LR, #224]\n\t"
|
||||
"STP X28, X29, [LR, #240]\n\t"
|
||||
"LDR LR, [SP], #16"
|
||||
);
|
||||
}
|
||||
|
||||
FORCE_INLINE void loadCtxTls() {
|
||||
asm("STR LR, [SP, #-16]!\n\t"
|
||||
"MRS LR, TPIDR_EL0\n\t"
|
||||
"LDP X0, X1, [LR, #16]\n\t"
|
||||
"LDP X2, X3, [LR, #32]\n\t"
|
||||
"LDP X4, X5, [LR, #48]\n\t"
|
||||
"LDP X6, X7, [LR, #64]\n\t"
|
||||
"LDP X8, X9, [LR, #80]\n\t"
|
||||
"LDP X10, X11, [LR, #96]\n\t"
|
||||
"LDP X12, X13, [LR, #112]\n\t"
|
||||
"LDP X14, X15, [LR, #128]\n\t"
|
||||
"LDP X16, X17, [LR, #144]\n\t"
|
||||
"LDP X18, X19, [LR, #160]\n\t"
|
||||
"LDP X20, X21, [LR, #176]\n\t"
|
||||
"LDP X22, X23, [LR, #192]\n\t"
|
||||
"LDP X24, X25, [LR, #208]\n\t"
|
||||
"LDP X26, X27, [LR, #224]\n\t"
|
||||
"LDP X28, X29, [LR, #240]\n\t"
|
||||
"LDR LR, [SP], #16"
|
||||
);
|
||||
}
|
||||
|
||||
void svcHandler(u64 pc, u32 svc) {
|
||||
volatile ThreadContext* ctx;
|
||||
asm("MRS %0, TPIDR_EL0":"=r"(ctx));
|
||||
ctx->pc = pc;
|
||||
ctx->commandId = svc;
|
||||
ctx->state = ThreadState::WaitKernel;
|
||||
while(ctx->state == ThreadState::WaitKernel);
|
||||
if(ctx->state == ThreadState::WaitRun) {
|
||||
return;
|
||||
} else {
|
||||
ctx->state = ThreadState::WaitKernel;
|
||||
while(ctx->state == ThreadState::WaitKernel);
|
||||
}
|
||||
}
|
||||
|
||||
void entry(u64 address) {
|
||||
volatile ThreadContext* ctx;
|
||||
asm("MRS %0, TPIDR_EL0":"=r"(ctx));
|
||||
while(true) {
|
||||
ctx->state = ThreadState::WaitInit;
|
||||
while (ctx->state == ThreadState::WaitInit);
|
||||
if (ctx->state == ThreadState::WaitRun)
|
||||
break;
|
||||
else if(ctx->state == ThreadState::WaitFunc) {
|
||||
if(ctx->commandId == static_cast<u32>(ThreadCall::Syscall)) {
|
||||
saveCtxStack();
|
||||
loadCtxTls();
|
||||
asm("STR LR, [SP, #-16]!\n\t"
|
||||
"MOV LR, SP\n\t"
|
||||
"SVC #0\n\t"
|
||||
"MOV SP, LR\n\t"
|
||||
"LDR LR, [SP], #16");
|
||||
saveCtxTls();
|
||||
loadCtxStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx->state = ThreadState::Running;
|
||||
asm("MOV LR, %0\n\t"
|
||||
"MOV X0, %1\n\t"
|
||||
"MOV X1, %2\n\t"
|
||||
"MOV X2, XZR\n\t"
|
||||
"MOV X3, XZR\n\t"
|
||||
"MOV X4, XZR\n\t"
|
||||
"MOV X5, XZR\n\t"
|
||||
"MOV X6, XZR\n\t"
|
||||
"MOV X7, XZR\n\t"
|
||||
"MOV X8, XZR\n\t"
|
||||
"MOV X9, XZR\n\t"
|
||||
"MOV X10, XZR\n\t"
|
||||
"MOV X11, XZR\n\t"
|
||||
"MOV X12, XZR\n\t"
|
||||
"MOV X13, XZR\n\t"
|
||||
"MOV X14, XZR\n\t"
|
||||
"MOV X15, XZR\n\t"
|
||||
"MOV X16, XZR\n\t"
|
||||
"MOV X17, XZR\n\t"
|
||||
"MOV X18, XZR\n\t"
|
||||
"MOV X19, XZR\n\t"
|
||||
"MOV X20, XZR\n\t"
|
||||
"MOV X21, XZR\n\t"
|
||||
"MOV X22, XZR\n\t"
|
||||
"MOV X23, XZR\n\t"
|
||||
"MOV X24, XZR\n\t"
|
||||
"MOV X25, XZR\n\t"
|
||||
"MOV X26, XZR\n\t"
|
||||
"MOV X27, XZR\n\t"
|
||||
"MOV X28, XZR\n\t"
|
||||
"MOV X29, XZR\n\t"
|
||||
"RET" :: "r"(address), "r"(ctx->registers.x0), "r"(ctx->registers.x1) : "x0","x1","lr");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
13
app/src/main/cpp/skyline/nce/guest.h
Normal file
13
app/src/main/cpp/skyline/nce/guest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace skyline {
|
||||
namespace guest {
|
||||
constexpr size_t saveCtxSize = 20 * sizeof(u32);
|
||||
constexpr size_t loadCtxSize = 20 * sizeof(u32);
|
||||
constexpr size_t svcHandlerSize = 200 * sizeof(u32);
|
||||
void entry(u64 address);
|
||||
extern "C" void saveCtx(void);
|
||||
extern "C" void loadCtx(void);
|
||||
void svcHandler(u64 pc, u32 svc);
|
||||
}
|
||||
}
|
149
app/src/main/cpp/skyline/nce/guest_common.h
Normal file
149
app/src/main/cpp/skyline/nce/guest_common.h
Normal file
@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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 This union holds 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__;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This enumeration is used to convey the state of a thread to the kernel
|
||||
*/
|
||||
enum class ThreadState : u32 {
|
||||
NotReady = 0, //!< The thread hasn't yet entered the entry handler
|
||||
Running = 1, //!< The thread is currently executing code
|
||||
WaitKernel = 2, //!< The thread is currently waiting on the kernel
|
||||
WaitRun = 3, //!< The thread should be ready to run
|
||||
WaitInit = 4, //!< The thread is waiting to be initialized
|
||||
WaitFunc = 5, //!< The kernel is waiting for the thread to run a function
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This enumeration holds the functions that can be run on the guest process
|
||||
*/
|
||||
enum class ThreadCall : u32 {
|
||||
Syscall = 0x100, //!< A linux syscall needs to be called from the guest
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This structure holds the context of a thread during kernel calls
|
||||
*/
|
||||
struct ThreadContext {
|
||||
ThreadState state; //!< The state of the guest
|
||||
u32 commandId; //!< The command ID of the current kernel call/function call
|
||||
u64 pc; //!< The program counter register on the guest
|
||||
Registers registers; //!< The general purpose registers on the guest
|
||||
u64 tpidrroEl0; //!< The value for TPIDRRO_EL0 for the current thread
|
||||
};
|
||||
}
|
400
app/src/main/cpp/skyline/nce/instr.h
Normal file
400
app/src/main/cpp/skyline/nce/instr.h
Normal file
@ -0,0 +1,400 @@
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
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 };
|
||||
enum S { Sp, Pc };
|
||||
}
|
||||
|
||||
namespace instr {
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a BRK instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
|
||||
*/
|
||||
struct Brk {
|
||||
/**
|
||||
* @brief Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
|
||||
* @param value The immediate value of the instruction
|
||||
*/
|
||||
explicit Brk(u16 value) {
|
||||
sig0 = 0x0; // First 5 bits of a BRK instruction are 0
|
||||
this->value = value;
|
||||
sig1 = 0x6A1; // Last 11 bits of a BRK instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid BRK instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig0 == 0x0 && sig1 == 0x6A1);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
u8 sig0 : 5;
|
||||
u32 value : 16;
|
||||
u16 sig1 : 11;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Brk) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
|
||||
*/
|
||||
struct Svc {
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid SVC instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig0 == 0x1 && sig1 == 0x6A0);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
u8 sig0 : 5;
|
||||
u32 value : 16;
|
||||
u16 sig1 : 11;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Svc) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
|
||||
*/
|
||||
struct Mrs {
|
||||
/**
|
||||
* @brief Creates a MRS instruction, used for generating BRK opcodes
|
||||
* @param srcReg The source system register
|
||||
* @param dstReg The destination Xn register
|
||||
*/
|
||||
Mrs(u32 srcReg, regs::X dstReg) {
|
||||
this->srcReg = srcReg;
|
||||
this->destReg = dstReg;
|
||||
sig = 0xD53; // Last 12 bits of a MRS instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid MRS instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig == 0xD53);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
u8 destReg : 5;
|
||||
u32 srcReg : 15;
|
||||
u16 sig : 12;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Mrs) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a B instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch.
|
||||
*/
|
||||
struct B {
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a B instruction with a specific offset
|
||||
* @param offset The offset to encode in the instruction (Should be 32-bit aligned)
|
||||
*/
|
||||
explicit B(i64 offset) {
|
||||
this->offset = static_cast<i32>(offset / 4);
|
||||
sig = 0x5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the offset of the instruction
|
||||
* @return The offset encoded within the instruction
|
||||
*/
|
||||
inline i32 Offset() {
|
||||
return offset * 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid Branch instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig == 0x5);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
i32 offset : 26;
|
||||
u8 sig : 6;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(B) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a BL instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch.
|
||||
*/
|
||||
struct BL {
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a BL instruction with a specific offset
|
||||
* @param offset The offset to encode in the instruction (Should be 32-bit aligned)
|
||||
*/
|
||||
explicit BL(i64 offset) {
|
||||
this->offset = static_cast<i32>(offset / 4);
|
||||
sig = 0x25;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the offset of the instruction
|
||||
* @return The offset encoded within the instruction
|
||||
*/
|
||||
inline i32 Offset() {
|
||||
return offset * 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid Branch Linked instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig == 0x85);
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
i32 offset : 26;
|
||||
u8 sig : 6;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(BL) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a MOVZ instruction. See https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/movz-move-wide-with-zero.
|
||||
*/
|
||||
struct Movz {
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a MOVZ instruction
|
||||
* @param destReg The destination Xn register to store the value in
|
||||
* @param imm16 The 16-bit value to store
|
||||
* @param shift The offset (in bits and 16-bit aligned) in the register to store the value at
|
||||
*/
|
||||
Movz(regs::X destReg, u16 imm16, u8 shift = 0) {
|
||||
this->destReg = static_cast<u8>(destReg);
|
||||
this->imm16 = imm16;
|
||||
hw = static_cast<u8>(shift / 16);
|
||||
sig = 0xA5;
|
||||
sf = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a MOVZ instruction
|
||||
* @param destReg The destination Wn register to store the value in
|
||||
* @param imm16 The 16-bit value to store
|
||||
* @param shift The offset (in bits and 16-bit aligned) in the register to store the value at
|
||||
*/
|
||||
Movz(regs::W destReg, u16 imm16, u8 shift = 0) {
|
||||
this->destReg = static_cast<u8>(destReg);
|
||||
this->imm16 = imm16;
|
||||
hw = static_cast<u8>(shift / 16);
|
||||
sig = 0xA5;
|
||||
sf = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the offset of the instruction
|
||||
* @return The offset encoded within the instruction
|
||||
*/
|
||||
inline u8 Shift() {
|
||||
return static_cast<u8>(hw * 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid MOVZ instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig == 0xA5);
|
||||
}
|
||||
|
||||
union {
|
||||
struct __attribute__((packed)) {
|
||||
u8 destReg : 5;
|
||||
u16 imm16 : 16;
|
||||
u8 hw : 2;
|
||||
u8 sig : 8;
|
||||
u8 sf : 1;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Movz) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a MOVK instruction. See https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/movk-move-wide-with-keep.
|
||||
*/
|
||||
struct Movk {
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a MOVK instruction
|
||||
* @param destReg The destination Xn register to store the value in
|
||||
* @param imm16 The 16-bit value to store
|
||||
* @param shift The offset (in bits and 16-bit aligned) in the register to store the value at
|
||||
*/
|
||||
Movk(regs::X destReg, u16 imm16, u8 shift = 0) {
|
||||
this->destReg = static_cast<u8>(destReg);
|
||||
this->imm16 = imm16;
|
||||
hw = static_cast<u8>(shift / 16);
|
||||
sig = 0xE5;
|
||||
sf = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a MOVK instruction
|
||||
* @param destReg The destination Wn register to store the value in
|
||||
* @param imm16 The 16-bit value to store
|
||||
* @param shift The offset (in bits and 16-bit aligned) in the register to store the value at
|
||||
*/
|
||||
Movk(regs::W destReg, u16 imm16, u8 shift = 0) {
|
||||
this->destReg = static_cast<u8>(destReg);
|
||||
this->imm16 = imm16;
|
||||
hw = static_cast<u8>(shift / 16);
|
||||
sig = 0xE5;
|
||||
sf = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the offset of the instruction
|
||||
* @return The offset encoded within the instruction
|
||||
*/
|
||||
inline u8 Shift() {
|
||||
return static_cast<u8>(hw * 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid MOVK instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig == 0xE5);
|
||||
}
|
||||
|
||||
union {
|
||||
struct __attribute__((packed)) {
|
||||
u8 destReg : 5;
|
||||
u16 imm16 : 16;
|
||||
u8 hw : 2;
|
||||
u8 sig : 8;
|
||||
u8 sf : 1;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Movk) == sizeof(u32));
|
||||
|
||||
const std::array<u32, 4> MoveU64Reg(regs::X destReg, u64 value) {
|
||||
union {
|
||||
u64 val;
|
||||
struct {
|
||||
u16 v0;
|
||||
u16 v16;
|
||||
u16 v32;
|
||||
u16 v64;
|
||||
};
|
||||
} val;
|
||||
val.val = value;
|
||||
std::array<u32, 4> instr;
|
||||
instr::Movz mov0(destReg, val.v0, 0);
|
||||
instr[0] = mov0.raw;
|
||||
instr::Movk mov16(destReg, val.v16, 16);
|
||||
instr[1] = mov16.raw;
|
||||
instr::Movk mov32(destReg, val.v32, 32);
|
||||
instr[2] = mov32.raw;
|
||||
instr::Movk mov64(destReg, val.v64, 48);
|
||||
instr[3] = mov64.raw;
|
||||
return instr;
|
||||
}
|
||||
|
||||
const std::array<u32, 2> MoveU32Reg(regs::X destReg, u32 value) {
|
||||
union {
|
||||
u32 val;
|
||||
struct {
|
||||
u16 v0;
|
||||
u16 v16;
|
||||
};
|
||||
} val;
|
||||
val.val = value;
|
||||
std::array<u32, 2> instr;
|
||||
instr::Movz mov0(destReg, val.v0, 0);
|
||||
instr[0] = mov0.raw;
|
||||
instr::Movk mov16(destReg, val.v16, 16);
|
||||
instr[1] = mov16.raw;
|
||||
return instr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A bit-field struct that encapsulates a MOV (Register) instruction. See https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/mov-register-move-register-an-alias-of-orr-shifted-register.
|
||||
*/
|
||||
struct Mov {
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a MOV (Register) instruction
|
||||
* @param destReg The destination Xn register to store the value in
|
||||
* @param srcReg The source Xn register to retrieve the value from
|
||||
*/
|
||||
Mov(regs::X destReg, regs::X srcReg) {
|
||||
this->destReg = static_cast<u8>(destReg);
|
||||
zeroReg = 0x1F;
|
||||
imm6 = 0;
|
||||
this->srcReg = static_cast<u8>(srcReg);
|
||||
sig = 0x150;
|
||||
sf = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a MOV instruction
|
||||
* @param destReg The destination Wn register to store the value in
|
||||
* @param srcReg The source Wn register to retrieve the value from
|
||||
*/
|
||||
Mov(regs::W destReg, regs::W srcReg) {
|
||||
this->destReg = static_cast<u8>(destReg);
|
||||
zeroReg = 0x1F;
|
||||
imm6 = 0;
|
||||
this->srcReg = static_cast<u8>(srcReg);
|
||||
sig = 0x150;
|
||||
sf = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if the opcode is valid or not
|
||||
* @return If the opcode represents a valid MOVZ instruction
|
||||
*/
|
||||
inline bool Verify() {
|
||||
return (sig == 0x150);
|
||||
}
|
||||
|
||||
union {
|
||||
struct __attribute__((packed)) {
|
||||
u8 destReg : 5;
|
||||
u8 zeroReg : 5;
|
||||
u8 imm6 : 6;
|
||||
u8 srcReg : 5;
|
||||
u16 sig : 10;
|
||||
u8 sf : 1;
|
||||
};
|
||||
u32 raw{};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Mov) == sizeof(u32));
|
||||
}
|
||||
}
|
@ -1,42 +1,39 @@
|
||||
#include "os.h"
|
||||
#include "kernel/svc.h"
|
||||
#include "loader/nro.h"
|
||||
#include "nce/guest.h"
|
||||
|
||||
namespace skyline::kernel {
|
||||
OS::OS(std::shared_ptr<JvmManager>& jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, thisThread, jvmManager, settings, logger), serviceManager(state) {}
|
||||
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, jvmManager, settings, logger), serviceManager(state) {}
|
||||
|
||||
void OS::Execute(const int romFd, const TitleFormat romType) {
|
||||
auto process = CreateProcess(constant::BaseAddr, constant::DefStackSize);
|
||||
std::shared_ptr<loader::Loader> loader;
|
||||
if (romType == TitleFormat::NRO) {
|
||||
loader::NroLoader loader(romFd);
|
||||
loader.LoadProcessData(process, state);
|
||||
loader = std::make_shared<loader::NroLoader>(romFd);
|
||||
} else
|
||||
throw exception("Unsupported ROM extension.");
|
||||
auto process = CreateProcess(loader->mainEntry, 0, constant::DefStackSize);
|
||||
loader->LoadProcessData(process, state);
|
||||
process->threadMap.at(process->pid)->Start(); // The kernel itself is responsible for starting the main thread
|
||||
state.nce->Execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed by all child processes after cloning
|
||||
*/
|
||||
int ExecuteChild(void *) {
|
||||
ptrace(PTRACE_TRACEME);
|
||||
asm volatile("Brk #0xFF"); // BRK #constant::brkRdy (So we know when the thread/process is ready)
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 address, size_t stackSize) {
|
||||
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 entry, u64 argument, size_t stackSize) {
|
||||
madvise(reinterpret_cast<void *>(constant::BaseAddr), constant::BaseEnd, MADV_DONTFORK);
|
||||
auto *stack = static_cast<u8 *>(mmap(nullptr, stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
madvise(stack, reinterpret_cast<size_t>(stack) + stackSize, MADV_DOFORK);
|
||||
if (stack == MAP_FAILED)
|
||||
throw exception("Failed to allocate stack memory");
|
||||
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
|
||||
munmap(stack, stackSize);
|
||||
throw exception("Failed to create guard pages");
|
||||
}
|
||||
pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FILES | CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission(true, true, 0), memory::Type::Reserved); // NOLINT(hicpp-signed-bitwise)
|
||||
tlsMem->guest = tlsMem->kernel;
|
||||
madvise(reinterpret_cast<void *>(tlsMem->guest.address), tlsMem->guest.size, MADV_DOFORK);
|
||||
pid_t pid = clone(reinterpret_cast<int (*)(void *)>(&guest::entry), stack + stackSize, CLONE_FILES | CLONE_FS | CLONE_SETTLS | SIGCHLD, reinterpret_cast<void *>(entry), nullptr, reinterpret_cast<void*>(tlsMem->guest.address)); // NOLINT(hicpp-signed-bitwise)
|
||||
if (pid == -1)
|
||||
throw exception("Call to clone() has failed: {}", strerror(errno));
|
||||
process = std::make_shared<kernel::type::KProcess>(state, pid, address, reinterpret_cast<u64>(stack), stackSize);
|
||||
process = std::make_shared<kernel::type::KProcess>(state, pid, argument, reinterpret_cast<u64>(stack), stackSize, tlsMem);
|
||||
state.logger->Debug("Successfully created process with PID: {}", pid);
|
||||
return process;
|
||||
}
|
||||
@ -54,16 +51,4 @@ namespace skyline::kernel {
|
||||
process->threadMap.erase(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void OS::SvcHandler(const u16 svc) {
|
||||
try {
|
||||
if (svc::SvcTable[svc]) {
|
||||
state.logger->Debug("SVC called 0x{:X}", svc);
|
||||
(*svc::SvcTable[svc])(state);
|
||||
} else
|
||||
throw exception("Unimplemented SVC 0x{:X}", svc);
|
||||
} catch(const exception& e) {
|
||||
throw exception("{} (SVC: 0x{:X})", e.what(), svc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "kernel/types/KProcess.h"
|
||||
#include "kernel/types/KThread.h"
|
||||
#include "services/serviceman.h"
|
||||
#include "nce.h"
|
||||
#include "gpu.h"
|
||||
|
||||
namespace skyline::kernel {
|
||||
@ -20,7 +19,6 @@ namespace skyline::kernel {
|
||||
|
||||
public:
|
||||
std::shared_ptr<type::KProcess> process; //!< The KProcess object for the emulator, representing the guest process
|
||||
std::shared_ptr<type::KThread> thisThread; //!< The corresponding KThread object of the thread that's called an SVC
|
||||
service::ServiceManager serviceManager; //!< This manages all of the service functions
|
||||
|
||||
/**
|
||||
@ -39,11 +37,12 @@ namespace skyline::kernel {
|
||||
|
||||
/**
|
||||
* @brief Creates a new process
|
||||
* @param address The address of the initial function
|
||||
* @param entry The entry point for the new process
|
||||
* @param argument The argument for the initial function
|
||||
* @param stackSize The size of the main stack
|
||||
* @return An instance of the KProcess of the created process
|
||||
*/
|
||||
std::shared_ptr<type::KProcess> CreateProcess(u64 address, size_t stackSize);
|
||||
std::shared_ptr<type::KProcess> CreateProcess(u64 entry, u64 argument, size_t stackSize);
|
||||
|
||||
/**
|
||||
* @brief Kill a particular thread
|
||||
|
Loading…
Reference in New Issue
Block a user