From 158d1b4fd919027e11c8bd24fe70852f241c3f80 Mon Sep 17 00:00:00 2001 From: NullVector Date: Fri, 18 Sep 2026 22:03:50 +0930 Subject: [PATCH] nxemu-cpu: more work for getting NCE closer to working (#265) --- external/ffmpeg | 2 +- src/nxemu-core/modules/system_modules.cpp | 2 +- src/nxemu-cpu/nce/arm_nce.cpp | 201 +++++++--------------- src/nxemu-cpu/nce/arm_nce.h | 99 ++++++----- src/nxemu-cpu/nce/arm_nce.s | 6 +- src/nxemu-cpu/nce/guest_context.h | 14 +- src/nxemu-cpu/nce/interpreter_visitor.cpp | 5 +- src/nxemu-cpu/nce/interpreter_visitor.h | 16 +- src/nxemu-cpu/nce/patcher.cpp | 58 +++---- src/nxemu-cpu/patch/patch_collection.cpp | 4 +- src/nxemu-module-spec/cpu.h | 12 ++ src/nxemu-os/core/hle/kernel/k_thread.cpp | 5 + src/nxemu-os/core/hle/kernel/k_thread.h | 18 +- src/nxemu-os/core/memory.cpp | 5 + src/nxemu-os/core/memory.h | 1 + 15 files changed, 190 insertions(+), 258 deletions(-) diff --git a/external/ffmpeg b/external/ffmpeg index 0c60c988..772cea1a 160000 --- a/external/ffmpeg +++ b/external/ffmpeg @@ -1 +1 @@ -Subproject commit 0c60c9885a66e2ffbeed95986aa434ed920a68ad +Subproject commit 772cea1ac12d5cd11ff17df97dc8a9858d9c1c01 diff --git a/src/nxemu-core/modules/system_modules.cpp b/src/nxemu-core/modules/system_modules.cpp index e41e64af..ae62e1ba 100644 --- a/src/nxemu-core/modules/system_modules.cpp +++ b/src/nxemu-core/modules/system_modules.cpp @@ -64,7 +64,7 @@ struct SystemModules::Impl : } } - bool IsValid() const + bool IsValid() const override { return valid; } diff --git a/src/nxemu-cpu/nce/arm_nce.cpp b/src/nxemu-cpu/nce/arm_nce.cpp index 123b3da7..7964a813 100644 --- a/src/nxemu-cpu/nce/arm_nce.cpp +++ b/src/nxemu-cpu/nce/arm_nce.cpp @@ -1,17 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include -#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" +#include "arm_dynarmic.h" +#include "nce/arm_nce.h" +#include "nce/interpreter_visitor.h" +#include "nce/patcher.h" +#include "yuzu_common/literals.h" +#include "yuzu_common/signal_chain.h" +#include "yuzu_common/yuzu_assert.h" #include #include @@ -25,7 +24,6 @@ struct sigaction g_orig_bus_action; struct sigaction g_orig_segv_action; // Verify assembly offsets. -using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters; static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext); static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock); static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic); @@ -40,30 +38,24 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) { using namespace Common::Literals; constexpr u32 StackSize = 128_KiB; +constexpr u64 NcePageSize = 0x1000; +constexpr u64 NcePageMask = NcePageSize - 1; } // namespace void* ArmNce::RestoreGuestContext(void* raw_context) { - // Retrieve the host context. auto& host_ctx = static_cast(raw_context)->uc_mcontext; - // Thread-local parameters will be located in x9. auto* tpidr = reinterpret_cast(host_ctx.regs[9]); auto* guest_ctx = static_cast(tpidr->native_context); - // Retrieve the host floating point state. auto* fpctx = GetFloatingPointState(host_ctx); - // Save host callee-saved registers. - std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->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)); + std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->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; @@ -71,19 +63,13 @@ void* ArmNce::RestoreGuestContext(void* raw_context) { fpctx->fpsr = guest_ctx->fpsr; std::memcpy(host_ctx.regs, guest_ctx->cpu_registers.data(), sizeof(host_ctx.regs)); std::memcpy(fpctx->vregs, guest_ctx->vector_registers.data(), sizeof(fpctx->vregs)); - - // 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 = static_cast(raw_context)->uc_mcontext; - - // Retrieve the host floating point state. auto* fpctx = GetFloatingPointState(host_ctx); - // Save all guest registers except tpidr_el0. std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs)); std::memcpy(guest_ctx->vector_registers.data(), fpctx->vregs, sizeof(fpctx->vregs)); guest_ctx->fpsr = fpctx->fpsr; @@ -92,19 +78,14 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) { 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(&fpctx->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); } @@ -112,62 +93,31 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi auto& host_ctx = static_cast(raw_context)->uc_mcontext; auto* info = static_cast(raw_info); - // We can't handle the access, so determine why we crashed. const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast(info->si_addr); - // 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(HaltReason::PrefetchAbort)); + guest_ctx->esr_el1.fetch_or((uint64_t)TranslateDynarmicHaltReason(CpuHaltReason::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); + thread_params.lock = SpinLockLocked; - // Return to host. SaveGuestContext(guest_ctx, raw_context); return false; } -bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { - auto& host_ctx = static_cast(raw_context)->uc_mcontext; - auto* fpctx = GetFloatingPointState(host_ctx); - auto& memory = guest_ctx->system->ApplicationMemory(); - - // Match and execute an instruction. - auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); - if (next_pc) { - host_ctx.pc = *next_pc; - return true; - } - - // We couldn't handle the access. - return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); +bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) +{ + UNIMPLEMENTED(); + return false; } bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { - auto* info = static_cast(raw_info); - - // Try to handle an invalid access. - // TODO: handle accesses which split a page? - const Common::ProcessAddress addr = - (reinterpret_cast(info->si_addr) & ~Memory::YUZU_PAGEMASK); - if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { - // We handled the access successfully and are returning to guest code. - return true; - } - - // We couldn't handle the access. - return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); + UNIMPLEMENTED(); + return false; } void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) { @@ -178,79 +128,47 @@ void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) { return g_orig_segv_action.sa_sigaction(sig, static_cast(raw_info), raw_context); } -void ArmNce::LockThread(Kernel::KThread* thread) { +void ArmNce::LockThread(IKernelThread* thread) { auto* thread_params = &thread->GetNativeExecutionParameters(); LockThreadParameters(thread_params); } -void ArmNce::UnlockThread(Kernel::KThread* thread) { +void ArmNce::UnlockThread(IKernelThread* thread) { auto* thread_params = &thread->GetNativeExecutionParameters(); 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(m_guest_ctx.esr_el1.exchange(0)); - if (True(hr)) { - return hr; - } - - // Get the thread context. - auto* thread_params = &thread->GetNativeExecutionParameters(); - auto* process = thread->GetOwnerProcess(); - - // Assign current members. - m_running_thread = thread; - m_guest_ctx.parent = this; - thread_params->native_context = &m_guest_ctx; - thread_params->tpidr_el0 = m_guest_ctx.tpidr_el0; - thread_params->tpidrro_el0 = m_guest_ctx.tpidrro_el0; - 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, &m_guest_ctx, it->second); - } else { - hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); - } - - // Unload members. - // The thread does not change, so we can persist the old reference. - m_running_thread = nullptr; - m_guest_ctx.tpidr_el0 = thread_params->tpidr_el0; - thread_params->native_context = nullptr; - thread_params->is_running = false; - - // Return the halt reason. - return hr; +CpuHaltReason ArmNce::RunThread(IKernelThread* thread) { + UNIMPLEMENTED(); + return CpuHaltReason::StepThread; } -HaltReason ArmNce::StepThread(Kernel::KThread* thread) { - return HaltReason::StepThread; +CpuHaltReason ArmNce::StepThread(IKernelThread* /*thread*/) { + return CpuHaltReason::StepThread; } -u32 ArmNce::GetSvcNumber() const { +uint32_t ArmNce::GetSvcNumber() const { return m_guest_ctx.svc; } -void ArmNce::GetSvcArguments(std::span args) const { +void ArmNce::GetSvcArguments(uint64_t (&args)[8]) const { for (size_t i = 0; i < 8; i++) { args[i] = m_guest_ctx.cpu_registers[i]; } } -void ArmNce::SetSvcArguments(std::span args) { +void ArmNce::SetSvcArguments(const uint64_t (&args)[8]) { 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(ICoreSystem& system, bool /*uses_wall_clock*/, IKernelProcess& process, std::size_t core_index) : + m_system(system), + m_core_index(core_index) +{ + m_guest_ctx.memory = &process.GetMemory(); + m_guest_ctx.parent = this; } ArmNce::~ArmNce() = default; @@ -313,12 +231,15 @@ void ArmNce::Initialize() { }); } -void ArmNce::SetTpidrroEl0(u64 value) { +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++) { +void ArmNce::GetContext(CpuThreadContext& 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]; @@ -326,14 +247,16 @@ void ArmNce::GetContext(Kernel::Svc::ThreadContext& ctx) const { 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; + std::memcpy(ctx.v, m_guest_ctx.vector_registers.data(), sizeof(ctx.v)); 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++) { +void ArmNce::SetContext(const CpuThreadContext& 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; @@ -341,31 +264,31 @@ void ArmNce::SetContext(const Kernel::Svc::ThreadContext& ctx) { 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; + std::memcpy(m_guest_ctx.vector_registers.data(), ctx.v, sizeof(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(HaltReason::BreakLoop)); +void ArmNce::SignalInterrupt(IKernelThread* thread) +{ + m_guest_ctx.esr_el1.fetch_or((uint64_t)TranslateDynarmicHaltReason(CpuHaltReason::BreakLoop)); - // Lock the thread context. auto* params = &thread->GetNativeExecutionParameters(); LockThreadParameters(params); - if (params->is_running) { - // We should signal to the running thread. - // The running thread will unlock the thread context. + if (params->is_running) + { syscall(SYS_tkill, m_thread_id, BreakFromRunCodeSignal); - } else { - // If the thread is no longer running, we have nothing to do. + } + else + { UnlockThreadParameters(params); } } -void ArmNce::ClearInstructionCache() { +void ArmNce::ClearInstructionCache() +{ // TODO: This is not possible to implement correctly on Linux because // we do not have any access to ic iallu. @@ -373,8 +296,14 @@ void ArmNce::ClearInstructionCache() { std::atomic_thread_fence(std::memory_order_seq_cst); } -void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) { +void ArmNce::InvalidateCacheRange(uint64_t /*addr*/, uint64_t /*size*/) +{ this->ClearInstructionCache(); } +void ArmNce::Release() +{ + delete this; +} + } // namespace Core diff --git a/src/nxemu-cpu/nce/arm_nce.h b/src/nxemu-cpu/nce/arm_nce.h index be9b304c..ef086d55 100644 --- a/src/nxemu-cpu/nce/arm_nce.h +++ b/src/nxemu-cpu/nce/arm_nce.h @@ -5,90 +5,87 @@ #include -#include "core/arm/arm_interface.h" -#include "core/arm/nce/guest_context.h" - -namespace Core::Memory { -class Memory; -} +#include "dynarmic/interface/halt_reason.h" +#include "nce/guest_context.h" +#include "yuzu_common/common_types.h" namespace Core { -class System; - -class ArmNce final : public ArmInterface { +class ArmNce final : public ICpuCore { public: - ArmNce(System& system, bool uses_wall_clock, std::size_t core_index); - ~ArmNce() override; + ArmNce(ICoreSystem & system, bool uses_wall_clock, IKernelProcess & process, std::size_t core_index); + ~ArmNce(); void Initialize() override; - Architecture GetArchitecture() const override { - return Architecture::AArch64; + ProcessorArchitecture GetArchitecture() const override + { + return ProcessorArchitecture::AArch64; } - HaltReason RunThread(Kernel::KThread* thread) override; - HaltReason StepThread(Kernel::KThread* thread) override; + CpuHaltReason RunThread(IKernelThread * thread) override; + CpuHaltReason StepThread(IKernelThread * thread) override; - void GetContext(Kernel::Svc::ThreadContext& ctx) const override; - void SetContext(const Kernel::Svc::ThreadContext& ctx) override; - void SetTpidrroEl0(u64 value) override; + void GetContext(CpuThreadContext & ctx) const override; + void SetContext(const CpuThreadContext & ctx) override; + void SetTpidrroEl0(uint64_t value) override; - void GetSvcArguments(std::span args) const override; - void SetSvcArguments(std::span args) override; - u32 GetSvcNumber() const override; + void GetSvcArguments(uint64_t (&args)[8]) const override; + void SetSvcArguments(const uint64_t (&args)[8]) override; + uint32_t GetSvcNumber() const override; - void SignalInterrupt(Kernel::KThread* thread) override; + void SignalInterrupt(IKernelThread * thread) override; void ClearInstructionCache() override; - void InvalidateCacheRange(u64 addr, std::size_t size) override; + void InvalidateCacheRange(uint64_t addr, uint64_t size) override; - void LockThread(Kernel::KThread* thread) override; - void UnlockThread(Kernel::KThread* thread) override; + void LockThread(IKernelThread * thread) override; + void UnlockThread(IKernelThread * thread) override; -protected: - const Kernel::DebugWatchpoint* HaltedWatchpoint() const override { + const CpuDebugWatchpoint * HaltedWatchpoint() const override + { return nullptr; } - void RewindBreakpointInstruction() override {} + void RewindBreakpointInstruction() override + { + } + void SetWatchpointArray(const CpuDebugWatchpoint * /*watchpoints*/, uint32_t /*count*/) override + { + } + + void Release() override; private: // Assembly definitions. - static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, GuestContext* ctx, - u64 trampoline_addr); - static HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr); + static Dynarmic::HaltReason ReturnToRunCodeByTrampoline(void * tpidr, GuestContext * ctx, uint64_t trampoline_addr); + static Dynarmic::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 GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context); - static void GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context); + static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void * info, void * raw_context); + static void BreakFromRunCodeSignalHandler(int sig, void * info, void * raw_context); + static void GuestAlignmentFaultSignalHandler(int sig, void * info, void * raw_context); + static void GuestAccessFaultSignalHandler(int sig, void * info, void * raw_context); - static void LockThreadParameters(void* tpidr); - static void UnlockThreadParameters(void* tpidr); + static void LockThreadParameters(void * tpidr); + static void UnlockThreadParameters(void * tpidr); private: - // 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); - static bool HandleGuestAlignmentFault(GuestContext* ctx, void* info, void* raw_context); - static bool HandleGuestAccessFault(GuestContext* ctx, void* info, void* raw_context); - static void HandleHostAlignmentFault(int sig, void* info, void* raw_context); - static void HandleHostAccessFault(int sig, void* info, void* raw_context); + static void * RestoreGuestContext(void * raw_context); + static void SaveGuestContext(GuestContext * ctx, void * raw_context); + static bool HandleFailedGuestFault(GuestContext * ctx, void * info, void * raw_context); + static bool HandleGuestAlignmentFault(GuestContext * ctx, void * info, void * raw_context); + static bool HandleGuestAccessFault(GuestContext * ctx, void * info, void * raw_context); + static void HandleHostAlignmentFault(int sig, void * info, void * raw_context); + static void HandleHostAccessFault(int sig, void * info, void * raw_context); public: - Core::System& m_system; + ICoreSystem & m_system; - // Members set on initialization. std::size_t m_core_index{}; pid_t m_thread_id{-1}; - // Core context. GuestContext m_guest_ctx{}; - Kernel::KThread* m_running_thread{}; + IKernelThread * m_running_thread{}; - // Stack for signal processing. std::unique_ptr m_stack{}; }; diff --git a/src/nxemu-cpu/nce/arm_nce.s b/src/nxemu-cpu/nce/arm_nce.s index c68c0594..37d592cb 100644 --- a/src/nxemu-cpu/nce/arm_nce.s +++ b/src/nxemu-cpu/nce/arm_nce.s @@ -1,14 +1,14 @@ /* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "core/arm/nce/arm_nce_asm_definitions.h" +#include "nce/arm_nce_asm_definitions.h" #define LOAD_IMMEDIATE_32(reg, val) \ mov reg, #(((val) >> 0x00) & 0xFFFF); \ movk reg, #(((val) >> 0x10) & 0xFFFF), lsl #16 -/* static HaltReason Core::ArmNce::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */ +/* static Dynarmic::HaltReason Core::ArmNce::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */ .section .text._ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, "ax", %progbits .global _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm .type _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function @@ -49,7 +49,7 @@ _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm: br x2 -/* static HaltReason Core::ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */ +/* static Dynarmic::HaltReason Core::ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */ .section .text._ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, "ax", %progbits .global _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv .type _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, %function diff --git a/src/nxemu-cpu/nce/guest_context.h b/src/nxemu-cpu/nce/guest_context.h index a7eadccc..aa2d376e 100644 --- a/src/nxemu-cpu/nce/guest_context.h +++ b/src/nxemu-cpu/nce/guest_context.h @@ -3,17 +3,17 @@ #pragma once +#include #include +#include -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "core/arm/arm_interface.h" -#include "core/arm/nce/arm_nce_asm_definitions.h" +#include "nce/arm_nce_asm_definitions.h" +#include "yuzu_common/common_types.h" +#include namespace Core { class ArmNce; -class System; struct HostContext { alignas(16) std::array host_saved_regs{}; @@ -36,8 +36,8 @@ struct GuestContext { std::atomic esr_el1{}; u32 nzcv{}; u32 svc{}; - System* system{}; - ArmNce* parent{}; + IMemory * memory{}; + ArmNce * parent{}; }; // Verify assembly offsets. diff --git a/src/nxemu-cpu/nce/interpreter_visitor.cpp b/src/nxemu-cpu/nce/interpreter_visitor.cpp index 5e548364..e949b0a1 100644 --- a/src/nxemu-cpu/nce/interpreter_visitor.cpp +++ b/src/nxemu-cpu/nce/interpreter_visitor.cpp @@ -2,8 +2,9 @@ // SPDX-FileCopyrightText: Copyright 2023 merryhime // SPDX-License-Identifier: GPL-2.0-or-later -#include "yuzu_common/bit_cast.h" #include "interpreter_visitor.h" +#include "yuzu_common/bit_cast.h" +#include "yuzu_common/logging/log.h" namespace Core { @@ -793,7 +794,7 @@ bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3 return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt); } -std::optional MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context, +std::optional MatchAndExecuteOneInstruction(IMemory& memory, mcontext_t* context, fpsimd_context* fpsimd_context) { // Construct the interpreter. std::span regs(reinterpret_cast(context->regs), 31); diff --git a/src/nxemu-cpu/nce/interpreter_visitor.h b/src/nxemu-cpu/nce/interpreter_visitor.h index f90d876a..00b8621f 100644 --- a/src/nxemu-cpu/nce/interpreter_visitor.h +++ b/src/nxemu-cpu/nce/interpreter_visitor.h @@ -4,20 +4,20 @@ #pragma once +#include #include +#include #include -#include "core/arm/nce/visitor_base.h" +#include "nce/visitor_base.h" +#include "yuzu_common/common_types.h" +#include namespace Core { -namespace Memory { -class Memory; -} - class InterpreterVisitor final : public VisitorBase { public: - explicit InterpreterVisitor(Core::Memory::Memory& memory, std::span regs, + explicit InterpreterVisitor(IMemory& memory, std::span regs, std::span fpsimd_regs, u64& sp, const u64& pc) : m_memory(memory), m_regs(regs), m_fpsimd_regs(fpsimd_regs), m_sp(sp), m_pc(pc) {} ~InterpreterVisitor() override = default; @@ -90,14 +90,14 @@ public: Vec Vt) override; private: - Core::Memory::Memory& m_memory; + IMemory& m_memory; std::span m_regs; std::span m_fpsimd_regs; u64& m_sp; const u64& m_pc; }; -std::optional MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context, +std::optional MatchAndExecuteOneInstruction(IMemory& memory, mcontext_t* context, fpsimd_context* fpsimd_context); } // namespace Core diff --git a/src/nxemu-cpu/nce/patcher.cpp b/src/nxemu-cpu/nce/patcher.cpp index 8ae1ffb7..f6cc58f0 100644 --- a/src/nxemu-cpu/nce/patcher.cpp +++ b/src/nxemu-cpu/nce/patcher.cpp @@ -1,24 +1,21 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "core/arm/nce/arm_nce.h" -#include "core/arm/nce/guest_context.h" -#include "core/arm/nce/instructions.h" -#include "core/arm/nce/patcher.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/hle/kernel/svc.h" +#include "arm_dynarmic.h" +#include "nce/guest_context.h" +#include "nce/instructions.h" +#include "nce/patcher.h" +#include "yuzu_common/alignment.h" #include "yuzu_common/arm64/native_clock.h" #include "yuzu_common/bit_cast.h" #include "yuzu_common/literals.h" +#include "yuzu_common/yuzu_assert.h" namespace Core::NCE { using namespace Common::Literals; using namespace oaknut::util; -using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters; - constexpr size_t MaxRelativeBranch = 128_MiB; constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32); @@ -38,10 +35,9 @@ Patcher::Patcher() : c(m_patch_instructions) { Patcher::~Patcher() = default; -bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, - const Kernel::CodeSet::Segment& code) { +bool Patcher::PatchText(const uint8_t * program_image, uint64_t image_size, uint64_t code_offset, + uint64_t code_size) { // If we have patched modules but cannot reach the new module, then it needs its own patcher. - const size_t image_size = program_image.size(); if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) { return false; } @@ -55,7 +51,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, curr_patch->m_branch_to_module_relocations.push_back({0, 0}); // Retrieve text segment data. - const auto text = std::span{program_image}.subspan(code.offset, code.size); + const auto text = std::span{program_image + code_offset, code_size}; const auto text_words = std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; @@ -119,15 +115,14 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, return true; } -bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, - const Kernel::CodeSet::Segment& code, - Kernel::PhysicalMemory& program_image, - EntryTrampolines* out_trampolines) { +bool Patcher::RelocateAndCopy(uint64_t load_base, uint64_t code_offset, uint64_t code_size, + uint8_t * program_image, uint64_t * image_size, + EntryTrampolines * out_trampolines) { const size_t patch_size = GetSectionSize(); - const size_t image_size = program_image.size(); + const size_t current_image_size = *image_size; // Retrieve text segment data. - const auto text = std::span{program_image}.subspan(code.offset, code.size); + const auto text = std::span{program_image + code_offset, code_size}; const auto text_words = std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; @@ -151,17 +146,17 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const auto RebasePatch = [&](ptrdiff_t patch_offset) { if (mode == PatchMode::PreText) { - return GetInteger(load_base) + patch_offset; + return load_base + patch_offset; } else { - return GetInteger(load_base) + total_program_size + patch_offset; + return load_base + total_program_size + patch_offset; } }; const auto RebasePc = [&](uintptr_t module_offset) { if (mode == PatchMode::PreText) { - return GetInteger(load_base) + patch_size + module_offset; + return load_base + patch_size + module_offset; } else { - return GetInteger(load_base) + module_offset; + return load_base + module_offset; } }; @@ -193,18 +188,18 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, // Remove the patched module size from the total. This is done so total_program_size // always represents the distance from the currently patched module to the patch section. - total_program_size -= image_size; + total_program_size -= current_image_size; // Only copy to the program image of the last module if (m_relocate_module_index == modules.size()) { if (this->mode == PatchMode::PreText) { - ASSERT(image_size == total_program_size); - std::memcpy(program_image.data(), m_patch_instructions.data(), + ASSERT(current_image_size == total_program_size); + std::memcpy(program_image, m_patch_instructions.data(), m_patch_instructions.size() * sizeof(u32)); } else { - program_image.resize(image_size + patch_size); - std::memcpy(program_image.data() + image_size, m_patch_instructions.data(), + std::memcpy(program_image + current_image_size, m_patch_instructions.data(), m_patch_instructions.size() * sizeof(u32)); + *image_size = current_image_size + patch_size; } return true; } @@ -213,7 +208,8 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, } size_t Patcher::GetSectionSize() const noexcept { - return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE); + constexpr u64 NcePageSize = 0x1000; + return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), NcePageSize); } void Patcher::WriteLoadContext() { @@ -310,7 +306,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { c.STR(W2, X1, offsetof(GuestContext, svc)); // We are calling a SVC. Clear esr_el1 and return it. - static_assert(std::is_same_v, u64>); + static_assert(sizeof(std::underlying_type_t) <= sizeof(u64)); oaknut::Label retry; c.ADD(X2, X1, offsetof(GuestContext, esr_el1)); c.l(retry); @@ -319,7 +315,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { c.CBNZ(W3, retry); // Add "calling SVC" flag. Since this is X0, this is now our return value. - c.ORR(X0, X0, static_cast(HaltReason::SupervisorCall)); + c.ORR(X0, X0, static_cast(TranslateDynarmicHaltReason(CpuHaltReason::SupervisorCall))); // Offset the GuestContext pointer to the HostContext member. // STP has limited range of [-512, 504] which we can't reach otherwise diff --git a/src/nxemu-cpu/patch/patch_collection.cpp b/src/nxemu-cpu/patch/patch_collection.cpp index 3e88d3ee..ee541f79 100644 --- a/src/nxemu-cpu/patch/patch_collection.cpp +++ b/src/nxemu-cpu/patch/patch_collection.cpp @@ -66,9 +66,9 @@ void PatchCollection::Relocate(int32_t patch_index, uint64_t load_base, uint8_t if (IKernelProcess * process = m_modules.OperatingSystem().CurrentProcess()) { - for (const auto & [module_text, patch_text] : trampolines) + for (Core::NCE::EntryTrampolines::const_iterator it = trampolines.begin(); it != trampolines.end(); ++it) { - process->RegisterPostHandler(module_text, patch_text); + process->RegisterPostHandler(it->first, it->second); } } diff --git a/src/nxemu-module-spec/cpu.h b/src/nxemu-module-spec/cpu.h index 64573154..a12f7fa9 100644 --- a/src/nxemu-module-spec/cpu.h +++ b/src/nxemu-module-spec/cpu.h @@ -82,6 +82,7 @@ nxinterface IMemory virtual uint32_t Read32(uint64_t addr) = 0; virtual uint64_t Read64(uint64_t addr) = 0; virtual bool ReadBlock(uint64_t src_addr, void * dest_buffer, uint64_t size) = 0; + virtual bool WriteBlock(uint64_t dest_addr, const void * src_buffer, uint64_t size) = 0; virtual void Write8(uint64_t addr, uint8_t value) = 0; virtual void Write16(uint64_t addr, uint16_t value) = 0; @@ -131,9 +132,20 @@ nxinterface IKernelProcess virtual void LogBacktrace(ICpuCore & cpuCore) = 0; }; +struct NativeExecutionParameters +{ + uint64_t tpidr_el0{}; + uint64_t tpidrro_el0{}; + void * native_context{}; + uint32_t lock{1}; + uint32_t is_running{}; + uint32_t magic{0x4D45584E}; // 'NXEM' +}; + nxinterface IKernelThread { virtual IKernelProcess * GetOwnerProcess() const = 0; + virtual NativeExecutionParameters & GetNativeExecutionParameters() = 0; }; struct CpuThreadContext diff --git a/src/nxemu-os/core/hle/kernel/k_thread.cpp b/src/nxemu-os/core/hle/kernel/k_thread.cpp index a19c77d0..9d8feb2c 100644 --- a/src/nxemu-os/core/hle/kernel/k_thread.cpp +++ b/src/nxemu-os/core/hle/kernel/k_thread.cpp @@ -103,6 +103,11 @@ KThread::KThread(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel}, m_activity_pause_lock{kernel} {} KThread::~KThread() = default; +IKernelProcess * KThread::GetOwnerProcess() const +{ + return (IKernelProcess *)m_parent; +} + Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess* owner, ThreadType type) { // Assert parameters are valid. diff --git a/src/nxemu-os/core/hle/kernel/k_thread.h b/src/nxemu-os/core/hle/kernel/k_thread.h index 7862fdfd..fdd18f75 100644 --- a/src/nxemu-os/core/hle/kernel/k_thread.h +++ b/src/nxemu-os/core/hle/kernel/k_thread.h @@ -365,10 +365,7 @@ public: return m_parent; } - IKernelProcess * GetOwnerProcess() const override - { - return (IKernelProcess *)m_parent; - } + IKernelProcess * GetOwnerProcess() const override; bool IsUserThread() const { @@ -758,18 +755,7 @@ public: } public: - // TODO: This shouldn't be defined in kernel namespace - struct NativeExecutionParameters - { - u64 tpidr_el0{}; - u64 tpidrro_el0{}; - void * native_context{}; - std::atomic lock{1}; - bool is_running{}; - u32 magic{Common::MakeMagic('Y', 'U', 'Z', 'U')}; - }; - - NativeExecutionParameters & GetNativeExecutionParameters() + NativeExecutionParameters & GetNativeExecutionParameters() override { return m_native_execution_parameters; } diff --git a/src/nxemu-os/core/memory.cpp b/src/nxemu-os/core/memory.cpp index c21e647f..a3fc9a66 100644 --- a/src/nxemu-os/core/memory.cpp +++ b/src/nxemu-os/core/memory.cpp @@ -1112,6 +1112,11 @@ bool Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_ return impl->WriteBlock(dest_addr, src_buffer, size); } +bool Memory::WriteBlock(uint64_t dest_addr, const void * src_buffer, uint64_t size) +{ + return WriteBlock(Common::ProcessAddress{dest_addr}, src_buffer, (std::size_t)size); +} + bool Memory::WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer, const std::size_t size) { return impl->WriteBlockUnsafe(dest_addr, src_buffer, size); diff --git a/src/nxemu-os/core/memory.h b/src/nxemu-os/core/memory.h index c84eed14..df8aeba4 100644 --- a/src/nxemu-os/core/memory.h +++ b/src/nxemu-os/core/memory.h @@ -349,6 +349,7 @@ public: * current process' address space. */ bool ReadBlock(uint64_t src_addr, void * dest_buffer, uint64_t size) override; + bool WriteBlock(uint64_t dest_addr, const void * src_buffer, uint64_t size) override; /** * Reads a contiguous block of bytes from the current process' address space.