You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
713 lines
24 KiB
713 lines
24 KiB
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#ifdef ARCHITECTURE_arm64
|
|
|
|
// 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 \
|
|
_Pragma("GCC diagnostic push") \
|
|
_Pragma("GCC diagnostic ignored \"-Wreturn-type\"") \
|
|
__attribute__((naked))
|
|
#elif defined(_MSC_VER)
|
|
#define YUZU_NAKED __declspec(naked)
|
|
#else
|
|
#error Unsupported compiler
|
|
#endif
|
|
|
|
#if defined(__clang__) || defined(__GNUC__)
|
|
#define YUZU_NAKED_END _Pragma("GCC diagnostic pop")
|
|
#else
|
|
#define YUZU_NAKED_END
|
|
#endif
|
|
|
|
#include <cinttypes>
|
|
#include <memory>
|
|
|
|
#include "common/signal_chain.h"
|
|
#include "core/arm/nce/arm_nce.h"
|
|
#include "core/arm/nce/interpreter_visitor.h"
|
|
#include "core/arm/nce/patcher.h"
|
|
#include "core/core.h"
|
|
#include "core/memory.h"
|
|
|
|
#include "core/hle/kernel/k_process.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#include <sys/syscall.h>
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
namespace Core {
|
|
|
|
namespace {
|
|
|
|
#ifndef _WIN32
|
|
struct sigaction g_orig_bus_action;
|
|
struct sigaction g_orig_segv_action;
|
|
#endif
|
|
|
|
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
|
|
|
using namespace Common::Literals;
|
|
constexpr u32 StackSize = 128_KiB;
|
|
|
|
} // namespace
|
|
|
|
YUZU_ALWAYS_INLINE
|
|
void* ArmNce::GetGuestParameters() {
|
|
void* nep; /* NativeExecutionParameters* */
|
|
#if defined(__APPLE__)
|
|
// https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/libsyscall/os/tsd.h#L156-L189
|
|
asm volatile(
|
|
"mrs %[out], TPIDRRO_EL0\n" // load pthreads TLS storage
|
|
"ldr %[out], [ %[out], #%[off] ]\n" // accessed like an array, so i * sizeof(u64)
|
|
: [out] "=&r"(nep)
|
|
: [off] "i"((ContextKey - 1) * 8)
|
|
: "memory");
|
|
#elif defined(_WIN32)
|
|
nep = TlsGetValue(ContextKey);
|
|
#elif defined(__linux__)
|
|
asm volatile(
|
|
"mrs %0, TPIDR_EL0\n"
|
|
: "=r"(nep));
|
|
#endif
|
|
return nep;
|
|
}
|
|
|
|
YUZU_ALWAYS_INLINE
|
|
void ArmNce::LockThreadParameters(void* tpidr) {
|
|
auto* nep = static_cast<NativeExecutionParameters*>(tpidr);
|
|
|
|
u32 value;
|
|
do {
|
|
do {
|
|
value = nep->lock.load(std::memory_order_acquire);
|
|
} while (value == SpinLockLocked);
|
|
} while (!nep->lock.compare_exchange_weak(value, SpinLockLocked,
|
|
std::memory_order_relaxed,
|
|
std::memory_order_relaxed));
|
|
}
|
|
|
|
YUZU_ALWAYS_INLINE
|
|
void ArmNce::UnlockThreadParameters(void* tpidr) {
|
|
static_cast<NativeExecutionParameters*>(tpidr)->lock.store(SpinLockUnlocked, std::memory_order_release);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
YUZU_NAKED
|
|
YUZU_NO_INLINE
|
|
HaltReason ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void *tpidr) {
|
|
// x0 - tid
|
|
// x1 - tpidr
|
|
|
|
// x9 - NativeExecutionParameters*
|
|
//
|
|
// 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(
|
|
"mov x9, x1\n" // move tpidr to x9 so it doesn't get clobbered
|
|
|
|
"mov x1, #%[sig]\n" // set x1 to SIGUSR2
|
|
#if defined(__linux__)
|
|
"mov x8, %[syscall]\n"
|
|
"svc #0\n"
|
|
"brk 0x0\n"
|
|
:: [syscall] "i"(__NR_tkill),
|
|
#elif defined(__APPLE__)
|
|
"mov x16, #328\n"
|
|
"svc #0x80\n"
|
|
"brk 0x0\n"
|
|
::
|
|
#else
|
|
"brk 0x0\n"
|
|
::
|
|
#endif
|
|
[sig] "i"(SIGUSR2)
|
|
: "memory"
|
|
);
|
|
}
|
|
YUZU_NAKED_END
|
|
#else
|
|
HaltReason ArmNce::ReturnToRunCodeByExceptionLevelChange(void* tid, void *tpidr) {
|
|
RaiseException(ExceptionLevelChangeSignal, 0, 0, nullptr); // TODO: pass tpidr through arguments?
|
|
__builtin_unreachable();
|
|
}
|
|
#endif
|
|
|
|
void ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void *info, void *raw_context) {
|
|
auto tpidr = static_cast<NativeExecutionParameters*>(RestoreGuestContext(raw_context));
|
|
|
|
#if !defined(__APPLE__) && !defined(_WIN32)
|
|
// Save old value of TPIDR_EL0, load guest one
|
|
u64 tpidr_el0;
|
|
asm volatile("mrs %0, TPIDR_EL0\n"
|
|
"msr TPIDR_EL0, %1\n"
|
|
: "=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_NAKED
|
|
YUZU_NO_INLINE
|
|
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"
|
|
"add x5, x2, #%[host_ctx] \n"
|
|
|
|
#if !defined(__APPLE__) && !defined(_WIN32)
|
|
// Load guest tpidr_el0
|
|
"mrs x4, TPIDR_EL0\n"
|
|
"msr TPIDR_EL0, x0\n"
|
|
// Store host sp and tpidr_el0
|
|
"stp x3, x4, [x2, #0xE0]\n"
|
|
#else
|
|
// is_actually_running = true
|
|
"mov w4, #1\n"
|
|
"strb w0, [ x0, #%[is_running_off] ]\n"
|
|
// Store host sp
|
|
"str x3, [x5, #0xE0]\n"
|
|
#endif
|
|
|
|
// 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))
|
|
#if defined(__APPLE__) || defined(_WIN32)
|
|
,[is_running_off] "i"(offsetof(NativeExecutionParameters, is_actually_running))
|
|
#endif
|
|
);
|
|
}
|
|
YUZU_NAKED_END
|
|
|
|
static_assert(offsetof(HostContext, host_sp) == 0xE0); // TODO: don't use magic number
|
|
|
|
#ifndef _WIN32
|
|
|
|
void ArmNce::BreakFromRunCodeSignalHandler(int sig, void *info, void *raw_context) {
|
|
NativeExecutionParameters* tpidr = static_cast<NativeExecutionParameters *>(GetGuestParameters());
|
|
#if defined(__APPLE__) || defined(_WIN32)
|
|
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
|
|
void* host_tpidr = reinterpret_cast<GuestContext*>(&tpidr->native_context)->host_ctx.host_tpidr_el0;
|
|
asm volatile(
|
|
"msr TPIDR_EL0, %[host_tpidr]\n"
|
|
:: [host_tpidr] "r"(host_tpidr));
|
|
#endif
|
|
SaveGuestContext(static_cast<GuestContext *>(tpidr->native_context), raw_context);
|
|
// SaveGuestContext loads host context, returning from here will enter host code.
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void ArmNce::GuestMemoryFaultSignalHandler(int sig, void* raw_info, void* raw_context) {
|
|
NativeExecutionParameters* nep = static_cast<NativeExecutionParameters*>(GetGuestParameters());
|
|
|
|
#if defined(__APPLE__) || defined(_WIN32)
|
|
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
|
|
void* host_tpidr = reinterpret_cast<GuestContext*>(&nep->native_context)->host_ctx.host_tpidr_el0;
|
|
asm volatile(
|
|
"msr TPIDR_EL0, %[host_tpidr]\n"
|
|
:: [host_tpidr] "r"(host_tpidr));
|
|
#endif
|
|
|
|
auto* guest_ctx = static_cast<GuestContext*>(nep->native_context);
|
|
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
|
|
|
|
#ifndef _WIN32
|
|
if (sig == SIGSEGV) {
|
|
#else
|
|
if (sig == static_cast<int>(EXCEPTION_ACCESS_VIOLATION)) {
|
|
#endif
|
|
// Try to handle an invalid access.
|
|
// TODO: handle accesses which split a page?
|
|
#ifndef _WIN32
|
|
const Common::ProcessAddress addr =
|
|
(reinterpret_cast<u64>(static_cast<siginfo_t*>(raw_info)->si_addr) & ~Memory::YUZU_PAGEMASK);
|
|
#else
|
|
const Common::ProcessAddress addr =
|
|
(reinterpret_cast<u64>(*static_cast<u64*>(raw_info)) & ~Memory::YUZU_PAGEMASK);
|
|
#endif
|
|
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
|
// We handled the access successfully and are returning to guest code.
|
|
goto ret;
|
|
}
|
|
#ifndef _WIN32
|
|
} else if (sig == SIGBUS) {
|
|
#else
|
|
} else if (sig == static_cast<int>(EXCEPTION_DATATYPE_MISALIGNMENT)) {
|
|
#endif
|
|
// Match and execute an instruction.
|
|
auto ctx = KernelContext(raw_context);
|
|
auto next_pc = MatchAndExecuteOneInstruction(memory, &ctx);
|
|
if (next_pc) {
|
|
// We handled the access successfully and are returning to guest code.
|
|
*ctx.pc() = *next_pc;
|
|
goto ret;
|
|
}
|
|
} else [[unlikely]] {
|
|
UNREACHABLE_MSG("unexpected signal {}", sig);
|
|
}
|
|
|
|
// We couldn't handle the access.
|
|
if (HandleFailedGuestFault(guest_ctx, raw_info, raw_context)) {
|
|
// Return to guest
|
|
goto ret;
|
|
}
|
|
// Otherwise HandleFailedGuestFault sets host context and returns to host
|
|
return;
|
|
|
|
ret:
|
|
#if defined(__linux__)
|
|
asm volatile(
|
|
"msr TPIDR_EL0, %0\n"
|
|
:: "r"(nep));
|
|
#else
|
|
nep->is_actually_running = true;
|
|
#endif
|
|
}
|
|
#ifndef _WIN32
|
|
else {
|
|
// Host fault, call original handler
|
|
if (sig == SIGSEGV) {
|
|
g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
|
} else if (sig == SIGBUS) {
|
|
g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
|
} else [[unlikely]] {
|
|
UNREACHABLE_MSG("unexpected signal {}", sig);
|
|
}
|
|
}
|
|
#else
|
|
is_host_fault = true;
|
|
#endif
|
|
}
|
|
|
|
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
|
// Retrieve the host context.
|
|
auto host_ctx = KernelContext(raw_context);
|
|
|
|
#ifndef _WIN32
|
|
// Thread-local parameters will be located in x9.
|
|
auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs()[9]);
|
|
#else
|
|
auto* tpidr = static_cast<NativeExecutionParameters*>(GetGuestParameters());
|
|
#endif
|
|
auto* guest_ctx = static_cast<GuestContext*>(tpidr->native_context);
|
|
|
|
// Save host callee-saved registers.
|
|
std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &host_ctx.vregs()[8],
|
|
sizeof(guest_ctx->host_ctx.host_saved_vregs));
|
|
std::memcpy(guest_ctx->host_ctx.host_saved_regs.data(), &host_ctx.regs()[19],
|
|
sizeof(guest_ctx->host_ctx.host_saved_regs));
|
|
|
|
// Save stack pointer.
|
|
guest_ctx->host_ctx.host_sp = *host_ctx.sp();
|
|
|
|
// Restore all guest state except tpidr_el0.
|
|
*host_ctx.sp() = guest_ctx->sp;
|
|
*host_ctx.pc() = guest_ctx->pc;
|
|
*host_ctx.pstate() = guest_ctx->pstate;
|
|
*host_ctx.fpcr() = guest_ctx->fpcr;
|
|
*host_ctx.fpsr() = guest_ctx->fpsr;
|
|
std::memcpy(host_ctx.regs(), guest_ctx->cpu_registers.data(), sizeof(guest_ctx->cpu_registers));
|
|
std::memcpy(host_ctx.vregs(), guest_ctx->vector_registers.data(), sizeof(guest_ctx->vector_registers));
|
|
|
|
// Return the new thread-local storage pointer.
|
|
return tpidr;
|
|
}
|
|
|
|
void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
|
// Retrieve the host context.
|
|
auto host_ctx = KernelContext(raw_context);
|
|
|
|
// Save all guest registers except tpidr_el0.
|
|
std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs(), sizeof(guest_ctx->cpu_registers));
|
|
std::memcpy(guest_ctx->vector_registers.data(), host_ctx.vregs(), sizeof(guest_ctx->vector_registers));
|
|
guest_ctx->fpsr = *host_ctx.fpsr();
|
|
guest_ctx->fpcr = *host_ctx.fpcr();
|
|
guest_ctx->pstate = *host_ctx.pstate();
|
|
guest_ctx->pc = *host_ctx.pc();
|
|
guest_ctx->sp = *host_ctx.sp();
|
|
|
|
// Restore stack pointer.
|
|
*host_ctx.sp() = guest_ctx->host_ctx.host_sp;
|
|
|
|
// Restore host callee-saved registers.
|
|
std::memcpy(&host_ctx.regs()[19], guest_ctx->host_ctx.host_saved_regs.data(),
|
|
sizeof(guest_ctx->host_ctx.host_saved_regs));
|
|
std::memcpy(&host_ctx.vregs()[8], guest_ctx->host_ctx.host_saved_vregs.data(),
|
|
sizeof(guest_ctx->host_ctx.host_saved_vregs));
|
|
|
|
// Return from the call on exit by setting pc to x30.
|
|
*host_ctx.pc() = guest_ctx->host_ctx.host_saved_regs[11];
|
|
|
|
// Clear esr_el1 and return it.
|
|
host_ctx.regs()[0] = guest_ctx->esr_el1.exchange(0);
|
|
}
|
|
|
|
bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
|
auto host_ctx = KernelContext(raw_context);
|
|
|
|
// We can't handle the access, so determine why we crashed.
|
|
#ifndef _WIN32
|
|
const bool is_prefetch_abort = *host_ctx.pc() == reinterpret_cast<u64>(static_cast<siginfo_t*>(raw_info)->si_addr);
|
|
#else
|
|
const bool is_prefetch_abort = *host_ctx.pc() == *static_cast<u64*>(raw_info);
|
|
#endif
|
|
|
|
// For data aborts, skip the instruction and return to guest code.
|
|
// This will allow games to continue in many scenarios where they would otherwise crash.
|
|
if (!is_prefetch_abort) {
|
|
*host_ctx.pc() += 4;
|
|
return true;
|
|
}
|
|
|
|
// This is a prefetch abort.
|
|
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
|
|
|
|
// Forcibly mark the context as locked. We are still running.
|
|
// We may race with SignalInterrupt here:
|
|
// - If we lose the race, then SignalInterrupt will send us a signal we are masking,
|
|
// and it will do nothing when it is unmasked, as we have already left guest code.
|
|
// - If we win the race, then SignalInterrupt will wait for us to unlock first.
|
|
auto& thread_params = guest_ctx->parent->m_running_thread->GetNativeExecutionParameters();
|
|
thread_params.lock.store(SpinLockLocked);
|
|
|
|
// Return to host.
|
|
SaveGuestContext(guest_ctx, raw_context);
|
|
return false;
|
|
}
|
|
|
|
void ArmNce::LockThread(Kernel::KThread* thread) {
|
|
auto* thread_params = &thread->GetNativeExecutionParameters();
|
|
LockThreadParameters(thread_params);
|
|
}
|
|
|
|
void ArmNce::UnlockThread(Kernel::KThread* thread) {
|
|
auto* thread_params = &thread->GetNativeExecutionParameters();
|
|
m_guest_ctx.tpidr_el0 = thread_params->tpidr_el0;
|
|
m_guest_ctx.tpidrro_el0 = thread_params->tpidrro_el0;
|
|
thread_params->native_context = nullptr;
|
|
UnlockThreadParameters(thread_params);
|
|
}
|
|
|
|
HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
|
|
// Check if we're already interrupted.
|
|
// If we are, we can just return immediately.
|
|
HaltReason hr = static_cast<HaltReason>(m_guest_ctx.esr_el1.exchange(0));
|
|
if (True(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Pre-fetch thread context data to improve cache locality
|
|
auto* thread_params = &thread->GetNativeExecutionParameters();
|
|
auto* process = thread->GetOwnerProcess();
|
|
|
|
#if defined(__APPLE__)
|
|
ASSERT(pthread_setspecific(ContextKey, &thread_params) == 0);
|
|
#elif defined(_WIN32)
|
|
ASSERT_MSG(TlsSetValue(ContextKey, &thread_params) == 0, "Failed to set TLS value: id {}, error {}", ContextKey, GetLastError());
|
|
#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;
|
|
|
|
// Critical section begins - minimize operations here
|
|
m_running_thread = thread;
|
|
m_guest_ctx.parent = this;
|
|
thread_params->native_context = &m_guest_ctx;
|
|
thread_params->tpidr_el0 = tpidr_el0_cache;
|
|
thread_params->tpidrro_el0 = tpidrro_el0_cache;
|
|
|
|
// Memory barrier to ensure visibility of changes
|
|
std::atomic_thread_fence(std::memory_order_release);
|
|
thread_params->is_running = true;
|
|
|
|
// TODO: finding and creating the post handler needs to be locked
|
|
// 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, 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
|
|
}
|
|
|
|
// Critical section for thread cleanup
|
|
std::atomic_thread_fence(std::memory_order_acquire);
|
|
|
|
// Cache values before releasing thread
|
|
const u64 final_tpidr_el0 = thread_params->tpidr_el0;
|
|
|
|
// Minimize critical section
|
|
thread_params->is_running = false;
|
|
thread_params->native_context = nullptr;
|
|
m_running_thread = nullptr;
|
|
|
|
// Non-critical updates can happen after releasing the thread
|
|
m_guest_ctx.tpidr_el0 = final_tpidr_el0;
|
|
|
|
// Return the halt reason.
|
|
return hr;
|
|
}
|
|
|
|
HaltReason ArmNce::StepThread(Kernel::KThread* thread) {
|
|
return HaltReason::StepThread;
|
|
}
|
|
|
|
u32 ArmNce::GetSvcNumber() const {
|
|
return m_guest_ctx.svc;
|
|
}
|
|
|
|
void ArmNce::GetSvcArguments(std::span<uint64_t, 8> args) const {
|
|
for (size_t i = 0; i < 8; i++) {
|
|
args[i] = m_guest_ctx.cpu_registers[i];
|
|
}
|
|
}
|
|
|
|
void ArmNce::SetSvcArguments(std::span<const uint64_t, 8> args) {
|
|
for (size_t i = 0; i < 8; i++) {
|
|
m_guest_ctx.cpu_registers[i] = args[i];
|
|
}
|
|
}
|
|
|
|
ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index)
|
|
: ArmInterface{uses_wall_clock}, m_system{system}, m_core_index{core_index} {
|
|
m_guest_ctx.system = &m_system;
|
|
}
|
|
|
|
ArmNce::~ArmNce() = default;
|
|
|
|
#ifdef _WIN32
|
|
LONG WINAPI ArmNce::VectoredExceptionHandler(PEXCEPTION_POINTERS info) {
|
|
DWORD code = info->ExceptionRecord->ExceptionCode;
|
|
|
|
if (code == EXCEPTION_ACCESS_VIOLATION || code == EXCEPTION_DATATYPE_MISALIGNMENT) {
|
|
GuestMemoryFaultSignalHandler(code, reinterpret_cast<void*>(&info->ExceptionRecord->ExceptionAddress), info->ContextRecord);
|
|
if (is_host_fault) {
|
|
is_host_fault = false;
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
} else if (code == ExceptionLevelChangeSignal) {
|
|
ReturnToRunCodeByExceptionLevelChangeSignalHandler(code, reinterpret_cast<void*>(&info->ExceptionRecord->ExceptionAddress), info->ContextRecord);
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
} else {
|
|
// other exception? let it pass
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
// https://github.com/apple-oss-distributions/libpthread/blob/42d026df5b07825070f60134b980a1ec2552dfee/src/pthread_tsd.c#L418-L435
|
|
extern "C" int pthread_key_init_np(int, void (*)(void *));
|
|
#endif
|
|
|
|
void ArmNce::Initialize() {
|
|
#ifdef __APPLE__
|
|
if (m_thread_id == -1) {
|
|
m_thread_id = pthread_mach_thread_np(pthread_self());
|
|
}
|
|
|
|
ASSERT(pthread_key_init_np(ContextKey, [](void*) -> void {}) == 0);
|
|
#elif defined(__linux__)
|
|
if (m_thread_id == -1) {
|
|
m_thread_id = gettid();
|
|
}
|
|
#elif defined(_WIN32)
|
|
if (m_thread_id == nullptr) {
|
|
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(),
|
|
&m_thread_id, 0, false, DUPLICATE_SAME_ACCESS);
|
|
}
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
// Configure signal stack.
|
|
if (!m_stack) {
|
|
m_stack = std::make_unique<u8[]>(StackSize);
|
|
|
|
stack_t ss{};
|
|
ss.ss_sp = m_stack.get();
|
|
ss.ss_size = StackSize;
|
|
sigaltstack(&ss, nullptr);
|
|
}
|
|
|
|
// Set up signals.
|
|
static std::once_flag flag;
|
|
std::call_once(flag, [] {
|
|
using HandlerType = decltype(sigaction::sa_sigaction);
|
|
|
|
sigset_t signal_mask;
|
|
sigemptyset(&signal_mask);
|
|
sigaddset(&signal_mask, SIGUSR2); // ReturnToCodeByExceptionLevel
|
|
sigaddset(&signal_mask, SIGURG); // BreakFromRunCode
|
|
sigaddset(&signal_mask, SIGBUS);
|
|
sigaddset(&signal_mask, SIGSEGV);
|
|
|
|
struct sigaction return_to_run_code_action {};
|
|
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
|
return_to_run_code_action.sa_sigaction = reinterpret_cast<HandlerType>(
|
|
&ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler);
|
|
return_to_run_code_action.sa_mask = signal_mask;
|
|
Common::SigAction(SIGUSR2, &return_to_run_code_action,
|
|
nullptr);
|
|
|
|
struct sigaction break_from_run_code_action {};
|
|
break_from_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
|
break_from_run_code_action.sa_sigaction =
|
|
reinterpret_cast<HandlerType>(&ArmNce::BreakFromRunCodeSignalHandler);
|
|
break_from_run_code_action.sa_mask = signal_mask;
|
|
Common::SigAction(SIGURG, &break_from_run_code_action, nullptr);
|
|
|
|
struct sigaction alignment_fault_action {};
|
|
alignment_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
|
alignment_fault_action.sa_sigaction =
|
|
reinterpret_cast<HandlerType>(&ArmNce::GuestMemoryFaultSignalHandler);
|
|
alignment_fault_action.sa_mask = signal_mask;
|
|
Common::SigAction(SIGBUS, &alignment_fault_action, nullptr);
|
|
|
|
struct sigaction access_fault_action {};
|
|
access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
|
access_fault_action.sa_sigaction =
|
|
reinterpret_cast<HandlerType>(&ArmNce::GuestMemoryFaultSignalHandler);
|
|
access_fault_action.sa_mask = signal_mask;
|
|
Common::SigAction(SIGSEGV, &access_fault_action, &g_orig_segv_action);
|
|
});
|
|
#else
|
|
static std::once_flag flag;
|
|
std::call_once(flag, [] { AddVectoredExceptionHandler(1, VectoredExceptionHandler); });
|
|
#endif
|
|
}
|
|
|
|
void ArmNce::SetTpidrroEl0(u64 value) {
|
|
m_guest_ctx.tpidrro_el0 = value;
|
|
}
|
|
|
|
void ArmNce::GetContext(Kernel::Svc::ThreadContext& ctx) const {
|
|
for (size_t i = 0; i < 29; i++) {
|
|
ctx.r[i] = m_guest_ctx.cpu_registers[i];
|
|
}
|
|
ctx.fp = m_guest_ctx.cpu_registers[29];
|
|
ctx.lr = m_guest_ctx.cpu_registers[30];
|
|
ctx.sp = m_guest_ctx.sp;
|
|
ctx.pc = m_guest_ctx.pc;
|
|
ctx.pstate = m_guest_ctx.pstate;
|
|
ctx.v = m_guest_ctx.vector_registers;
|
|
ctx.fpcr = m_guest_ctx.fpcr;
|
|
ctx.fpsr = m_guest_ctx.fpsr;
|
|
ctx.tpidr = m_guest_ctx.tpidr_el0;
|
|
}
|
|
|
|
void ArmNce::SetContext(const Kernel::Svc::ThreadContext& ctx) {
|
|
for (size_t i = 0; i < 29; i++) {
|
|
m_guest_ctx.cpu_registers[i] = ctx.r[i];
|
|
}
|
|
m_guest_ctx.cpu_registers[29] = ctx.fp;
|
|
m_guest_ctx.cpu_registers[30] = ctx.lr;
|
|
m_guest_ctx.sp = ctx.sp;
|
|
m_guest_ctx.pc = ctx.pc;
|
|
m_guest_ctx.pstate = ctx.pstate;
|
|
m_guest_ctx.vector_registers = ctx.v;
|
|
m_guest_ctx.fpcr = ctx.fpcr;
|
|
m_guest_ctx.fpsr = ctx.fpsr;
|
|
m_guest_ctx.tpidr_el0 = ctx.tpidr;
|
|
}
|
|
|
|
void ArmNce::SignalInterrupt(Kernel::KThread* thread) {
|
|
// Add break loop condition.
|
|
m_guest_ctx.esr_el1.fetch_or(static_cast<u64>(HaltReason::BreakLoop));
|
|
|
|
auto* params = &thread->GetNativeExecutionParameters();
|
|
LockThreadParameters(params);
|
|
|
|
// Ensure visibility of is_running after lock acquire
|
|
std::atomic_thread_fence(std::memory_order_acquire);
|
|
|
|
if (params->is_running) {
|
|
// We should signal to the running thread.
|
|
// The running thread will unlock the thread context.
|
|
#if defined(__linux__)
|
|
syscall(SYS_tkill, m_thread_id, SIGURG); // BreakFromRunCodeSignal
|
|
#elif defined(__APPLE__)
|
|
asm volatile(
|
|
"mov x0, %0\n" // m_thread_id
|
|
"mov x1, %1\n" // BreakFromRunCodeSignal
|
|
"mov x16, #328\n" // syscall code for __pthread_kill
|
|
"svc #0x80\n"
|
|
:: "r"(static_cast<u64>(m_thread_id)), "r"(static_cast<u64>(SIGURG))
|
|
: "x0", "x1", "x16", "memory", "cc");
|
|
#elif defined(_WIN32)
|
|
// TODO: use SetThreadState to emulate BreakFromRunCodeSignalHandler
|
|
SuspendThread(m_thread_id);
|
|
UnlockThreadParameters(params);
|
|
#endif
|
|
} else {
|
|
// If the thread is no longer running, we have nothing to do.
|
|
UnlockThreadParameters(params);
|
|
}
|
|
}
|
|
|
|
void ArmNce::ClearInstructionCache() {
|
|
// Ensure all previous memory operations complete
|
|
asm volatile("dsb ish\n"
|
|
"dsb ish\n"
|
|
"isb" ::: "memory");
|
|
}
|
|
|
|
void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) {
|
|
ClearInstructionCache();
|
|
}
|
|
|
|
} // namespace Core
|
|
|
|
#endif // #ifdef ARCHITECTURE_arm64
|