Browse Source

[nce] Clean up loose ends

NCE should be fully working at least on Linux (4KiB) and Android
remotes/1785372757367212240/tmp_refs/heads/variable-page-size
Exverge 1 month ago
parent
commit
bdd9278c8b
No known key found for this signature in database GPG Key ID: DAD399BCC5FB77E4
  1. 2
      src/common/host_memory.cpp
  2. 243
      src/core/arm/nce/arm_nce.cpp
  3. 7
      src/core/arm/nce/arm_nce.h
  4. 2
      src/core/arm/nce/patcher.cpp

2
src/common/host_memory.cpp

@ -436,8 +436,8 @@ static void* ChooseVirtualBase(size_t virtual_size) {
if (map_pointer != MAP_FAILED) {
munmap(map_pointer, virtual_size);
}
}
#endif
}
return MAP_FAILED;
}

243
src/core/arm/nce/arm_nce.cpp

@ -6,6 +6,19 @@
#ifdef __aarch64__
// Certain functions have to be marked naked so that the compiler doesn't touch the stack
// or implement a return (we "artificially" return later by setting PC to the LR value)
#if defined(__CLANG__) || defined(__GNUC__)
#define YUZU_NAKED __attribute__((naked))
#elif _MSC_VER
// todo: windows support?? it supports native context switching and signal handling
// https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext
#define YUZU_NAKED __declspec(naked)
#else
#error Unsupported compiler
#endif
#include <cinttypes>
#include <memory>
@ -75,6 +88,141 @@ void ArmNce::UnlockThreadParameters(void* tpidr) {
static_cast<NativeExecutionParameters*>(tpidr)->lock.store(SpinLockUnlocked, std::memory_order_release);
}
YUZU_NO_INLINE
YUZU_NAKED
HaltReason ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void *tpidr) {
// x0 - tid
// x1 - tpidr
// x19 - callee-saved register and our tpidr storage
//
// uses tkill on linux and pthread_kill on macOS, both have the same signature:
/* syscall (u32 tid, u64 signal) */
// tid is already in x0 so we don't have to explicitly pass it
asm volatile(
"str x19, [SP, #-0x10]!\n"
"mov x19, x1\n" // move tpidr to x19 so it doesn't get clobbered
"mov x1, #%[sig]\n" // set x1 to SIGUSR2
#ifdef __linux__
"mov x8, %[syscall]\n"
"svc #0\n"
"brk 0x0\n"
:: [syscall] "i"(__NR_tkill),
#elif defined(__APPLE__)
"mov x8, #328\n"
"svc #0\n"
"brk 0x0\n"
::
#endif
[sig] "i"(SIGUSR2)
);
}
void ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void *info, void *raw_context) {
auto tpidr = static_cast<NativeExecutionParameters*>(RestoreGuestContext(raw_context));
RestoreGuestContext(raw_context);
#ifndef __APPLE__
// Save old value of TPIDR_EL0, load guest one
u64 tpidr_el0;
asm volatile("mrs %0, TPIDR_EL0"
"msr TPIDR_EL0, %1"
: "=r"(tpidr_el0)
: "r"(tpidr));
tpidr->tpidr_el0 = tpidr_el0;
#else
tpidr->is_actually_running = true;
#endif
UnlockThreadParameters(tpidr);
// sigaction restores context and returns to guest
}
YUZU_NO_INLINE
YUZU_NAKED
HaltReason ArmNce::ReturnToRunCodeByTrampoline(void *tpidr, u64 trampoline_addr) {
// x0 - NativeExecutionParameters*
// x1 - addr
// x2 - GuestContext*
// x3 - Host SP
// x4 - Host TPIDR_EL0 on non-Apple, is_actually_running on Apple
// x5 - Scratch register
asm volatile(
"mov x3, SP\n"
"ldr x2, [ x0, #%[ctx_off] ]\n"
#ifndef __APPLE__
// Load guest tpidr_el0
"mrs x4, TPIDR_EL0\n"
"msr TPIDR_EL0, x0\n"
// Store host sp and tpidr_el0
"stp x3, x4, [ x2, #%[host_sp] ]\n"
#else
// is_actually_running = true
"mov w4, #1\n"
"strb w0, [ x0, #%[is_running_off] ]\n"
// Store host sp
"str x3, [ x2, #(%[host_ctx] + 0xE0) ]\n"
#endif
"add x5, x2, #%[host_ctx]\n"
// Save callee-saved host GPR registers
"stp x19, x20, [x5, #0x0]\n"
"stp x21, x22, [x5, #0x10]\n"
"stp x23, x24, [x5, #0x20]\n"
"stp x25, x26, [x5, #0x30]\n"
"stp x27, x28, [x5, #0x40]\n"
"stp x29, x30, [x5, #0x50]\n"
// Save callee-saved host vector registers
"stp q8, q9, [x5, #(0x60)]\n"
"stp q10, q11, [x5, #(0x80)]\n"
"stp q12, q13, [x5, #(0xA0)]\n"
"stp q14, q15, [x5, #(0xC0)]\n"
"ldr x5, [ x2, #%[sp_off] ]\n"
"mov SP, x5\n"
"br x1\n"
"brk 0x0\n"
:: [ctx_off] "i"(offsetof(NativeExecutionParameters, native_context)),
[sp_off] "i"(offsetof(GuestContext, sp)),
[host_ctx] "i"(offsetof(GuestContext, host_ctx)),
#ifdef __APPLE__
[is_running_off] "i"(offsetof(NativeExecutionParameters, is_actually_running))
#endif
);
}
static_assert(offsetof(HostContext, host_sp) == 0xE0);
void ArmNce::BreakFromRunCodeSignalHandler(int sig, void *info, void *raw_context) {
NativeExecutionParameters* tpidr = reinterpret_cast<NativeExecutionParameters *>(GetGuestParameters());
#ifdef __APPLE__
if (tpidr->is_actually_running) {
tpidr->is_actually_running = false;
#else
if (tpidr->magic == Common::MakeMagic('Y', 'U', 'Z', 'U')) {
// Load the host's TPIDR_EL0 value
u64 scratch;
asm volatile(
"ldr %[scratch], [ %[tpidr], #[off] ]\n"
"msr TPIDR_EL0, %[scratch]\n"
: [scratch] "=&r"(scratch),
: [tpidr] "r"(nep),
: "memory"
);
#endif
SaveGuestContext(static_cast<GuestContext *>(tpidr->native_context), raw_context);
// Returning from here will enter host code.
}
}
void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_context) {
DEBUG_ASSERT(sig == SIGSEGV);
@ -82,6 +230,7 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
#ifdef __APPLE__
if (nep->is_actually_running) {
nep->is_actually_running = false;
#else
if (nep->magic == Common::MakeMagic('Y', 'U', 'Z', 'U')) {
// Load the host's TPIDR_EL0 value
@ -135,7 +284,7 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
"msr TPIDR_EL0, %0\n"
:: "r"(nep));
#else
(void)0;
nep->is_actually_running = true;
#endif
} else {
// Host fault, call original handler
@ -149,85 +298,6 @@ void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_co
}
}
// This function has to be marked naked so that the compiler doesn't touch the stack
// or implement a return (we "artificially" return later by setting PC to the LR value)
YUZU_NO_INLINE
__attribute__((naked))
HaltReason ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void *tpidr) {
// x0 - tid
// x1 - tpidr
// x19 - callee-saved register and our tpidr storage
//
// uses tkill on linux and pthread_kill on macOS, both have the same signature:
// syscall (u32 tid, u64 signal)
asm volatile(
"str x19, [SP, #-0x10]!\n"
"mov x19, x1\n" // move tpidr to x19 so it doesn't get clobbered
"mov x1, #%[sig]\n" // set x1 to SIGUSR2
#ifdef __linux__
"mov x8, %[syscall]\n"
"svc #0\n"
"brk 0x0\n"
:: [syscall] "i"(__NR_tkill),
#elif defined(__APPLE__)
"mov x8, #328\n"
"svc #0\n"
"brk 0x0\n"
::
#endif
[sig] "i"(SIGUSR2)
: "memory"
);
}
void ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void *info, void *raw_context) {
auto tpidr = static_cast<NativeExecutionParameters*>(RestoreGuestContext(raw_context));
RestoreGuestContext(raw_context);
#ifndef __APPLE__
// Save old value of TPIDR_EL0, load guest one
u64 tpidr_el0;
asm volatile("mrs %0, TPIDR_EL0"
"msr TPIDR_EL0, %1"
: "=r"(tpidr_el0)
: "r"(tpidr));
tpidr->tpidr_el0 = tpidr_el0;
#else
tpidr->is_actually_running = true;
#endif
// todo: is Lock called?
UnlockThreadParameters(tpidr);
// sigaction restores context and returns to guest
}
void ArmNce::BreakFromRunCodeSignalHandler(int sig, void *info, void *raw_context) {
NativeExecutionParameters* tpidr = reinterpret_cast<NativeExecutionParameters *>(GetGuestParameters());
#ifdef __APPLE__
if (tpidr->is_actually_running) {
#else
if (tpidr->magic == Common::MakeMagic('Y', 'U', 'Z', 'U')) {
// Load the host's TPIDR_EL0 value
u64 scratch;
asm volatile(
"ldr %[scratch], [ %[tpidr], #[off] ]\n"
"msr TPIDR_EL0, %[scratch]\n"
: [scratch] "=&r"(scratch),
: [tpidr] "r"(nep),
: "memory"
);
#endif
SaveGuestContext(static_cast<GuestContext *>(tpidr->native_context), raw_context);
// Returning from here will enter host code.
}
}
HaltReason ArmNce::ReturnToRunCodeByTrampoline(void *tpidr, GuestContext *ctx, u64 trampoline_addr) {
// todo
return HaltReason::DataAbort;
}
void* ArmNce::RestoreGuestContext(void* raw_context) {
// Retrieve the host context.
auto host_ctx = KernelContext(&static_cast<ucontext_t*>(raw_context)->uc_mcontext);
@ -343,6 +413,10 @@ HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
auto* thread_params = &thread->GetNativeExecutionParameters();
auto* process = thread->GetOwnerProcess();
#ifdef __APPLE__
ASSERT(pthread_setspecific(CONTEXT_KEY, &thread_params) == 0);
#endif
// Move non-critical operations outside the locked section
const u64 tpidr_el0_cache = m_guest_ctx.tpidr_el0;
const u64 tpidrro_el0_cache = m_guest_ctx.tpidrro_el0;
@ -362,7 +436,7 @@ HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
// to deal with dynamic loading of NROs.
const auto& post_handlers = process->GetPostHandlers();
if (auto it = post_handlers.find(m_guest_ctx.pc); it != post_handlers.end()) {
hr = ReturnToRunCodeByTrampoline(thread_params, &m_guest_ctx, it->second);
hr = ReturnToRunCodeByTrampoline(thread_params, it->second);
} else {
hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); // Android: Use "process handle SIGUSR2 -n true -p true -s false" (and SIGURG) in LLDB when debugging
}
@ -424,14 +498,11 @@ void ArmNce::Initialize() {
}
ASSERT(pthread_key_init_np(CONTEXT_KEY, [](void*) -> void {}) == 0);
#endif
#elif defined(__linux__)
if (m_thread_id == -1) {
#if defined(__linux__)
m_thread_id = gettid();
#else
m_thread_id = pthread_mach_thread_np(pthread_self());
#endif
}
#endif
// Configure signal stack.
if (!m_stack) {

7
src/core/arm/nce/arm_nce.h

@ -70,22 +70,21 @@ protected:
private:
// Only confirmed to be valid on Apple systems.
static void* GetGuestParameters();
static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, GuestContext* ctx,
u64 trampoline_addr);
static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, u64 trampoline_addr);
static HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr);
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
void* raw_context);
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
static void GuestMemoryFaultSignalHandler(int sig, void* info, void* raw_context);
static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context);
static void LockThreadParameters(void* tpidr);
static void UnlockThreadParameters(void* tpidr);
// C++ implementation functions for assembly definitions.
static void* RestoreGuestContext(void* raw_context);
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context);
public:
Core::System& m_system;

2
src/core/arm/nce/patcher.cpp

@ -493,10 +493,10 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut
cg.ADD(X1, X1, offsetof(GuestContext, host_ctx));
// Reload host TPIDR_EL0 and SP.
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
cg.MOV(SP, X2);
#ifndef __APPLE__
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
#endif

Loading…
Cancel
Save