From 19619b1b14ce347b09118626efc57f8551f2a5bf Mon Sep 17 00:00:00 2001 From: Liam Date: Fri, 17 Nov 2023 23:44:53 +0200 Subject: arm: Implement native code execution backend --- src/core/loader/nso.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) (limited to 'src/core/loader/nso.cpp') diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 1350da8dca..34b10ef2e4 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -20,6 +20,10 @@ #include "core/loader/nso.h" #include "core/memory.h" +#ifdef ARCHITECTURE_arm64 +#include "core/arm/nce/patch.h" +#endif + namespace Loader { namespace { struct MODHeader { @@ -72,7 +76,8 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) { std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, VAddr load_base, bool should_pass_arguments, bool load_into_process, - std::optional pm) { + std::optional pm, + Core::NCE::Patcher* patch) { if (nso_file.GetSize() < sizeof(NSOHeader)) { return std::nullopt; } @@ -86,6 +91,16 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: return std::nullopt; } + // Allocate some space at the beginning if we are patching in PreText mode. + const size_t module_start = [&]() -> size_t { +#ifdef ARCHITECTURE_arm64 + if (patch && patch->Mode() == Core::NCE::PatchMode::PreText) { + return patch->SectionSize(); + } +#endif + return 0; + }(); + // Build program image Kernel::CodeSet codeset; Kernel::PhysicalMemory program_image; @@ -95,11 +110,12 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: if (nso_header.IsSegmentCompressed(i)) { data = DecompressSegment(data, nso_header.segments[i]); } - program_image.resize(nso_header.segments[i].location + static_cast(data.size())); - std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(), - data.size()); - codeset.segments[i].addr = nso_header.segments[i].location; - codeset.segments[i].offset = nso_header.segments[i].location; + program_image.resize(module_start + nso_header.segments[i].location + + static_cast(data.size())); + std::memcpy(program_image.data() + module_start + nso_header.segments[i].location, + data.data(), data.size()); + codeset.segments[i].addr = module_start + nso_header.segments[i].location; + codeset.segments[i].offset = module_start + nso_header.segments[i].location; codeset.segments[i].size = nso_header.segments[i].size; } @@ -118,7 +134,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: } codeset.DataSegment().size += nso_header.segments[2].bss_size; - const u32 image_size{ + u32 image_size{ PageAlignSize(static_cast(program_image.size()) + nso_header.segments[2].bss_size)}; program_image.resize(image_size); @@ -139,6 +155,32 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); } +#ifdef ARCHITECTURE_arm64 + // If we are computing the process code layout and using nce backend, patch. + const auto& code = codeset.CodeSegment(); + if (patch && patch->Mode() == Core::NCE::PatchMode::None) { + // Patch SVCs and MRS calls in the guest code + patch->PatchText(program_image, code); + + // Add patch section size to the module size. + image_size += patch->SectionSize(); + } else if (patch) { + // Relocate code patch and copy to the program_image. + patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers()); + + // Update patch section. + auto& patch_segment = codeset.PatchSegment(); + patch_segment.addr = patch->Mode() == Core::NCE::PatchMode::PreText ? 0 : image_size; + patch_segment.size = static_cast(patch->SectionSize()); + + // Add patch section size to the module size. In PreText mode image_size + // already contains the patch segment as part of module_start. + if (patch->Mode() == Core::NCE::PatchMode::PostData) { + image_size += patch_segment.size; + } + } +#endif + // If we aren't actually loading (i.e. just computing the process code layout), we are done if (!load_into_process) { return load_base + image_size; -- cgit v1.2.3 From 3814ca9d84c60de40ef232bb5675eb0609617bc5 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Sun, 19 Nov 2023 11:21:53 +0200 Subject: Address some review comments --- .codespellrc | 2 +- src/common/host_memory.cpp | 3 +++ src/common/settings.h | 2 +- src/common/signal_chain.h | 4 ++-- src/core/arm/nce/arm_nce.cpp | 5 ++--- src/core/arm/nce/patch.cpp | 5 ++--- src/core/arm/nce/patch.h | 16 +++++----------- src/core/core.cpp | 1 + src/core/hle/kernel/k_page_table_base.cpp | 2 ++ src/core/hle/kernel/k_process.h | 4 ---- src/core/loader/nro.cpp | 6 +++--- src/core/loader/nso.cpp | 15 ++++++++------- 12 files changed, 30 insertions(+), 35 deletions(-) (limited to 'src/core/loader/nso.cpp') diff --git a/.codespellrc b/.codespellrc index 9ff49898b8..d1f9984496 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,4 +3,4 @@ [codespell] skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res -ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink,nce +ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nce,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 8a869e5589..f140777506 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -409,11 +409,14 @@ static void* ChooseVirtualBase(size_t virtual_size) { return MAP_FAILED; } + #else + static void* ChooseVirtualBase(size_t virtual_size) { return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); } + #endif class HostMemory::Impl { diff --git a/src/common/settings.h b/src/common/settings.h index 648e0be0d9..fea639ee30 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -181,7 +181,7 @@ struct Values { // Cpu SwitchableSetting cpu_backend{ - linkage, CpuBackend::Nce, CpuBackend::Dynarmic, + linkage, CpuBackend::Dynarmic, CpuBackend::Dynarmic, #ifdef ARCHITECTURE_arm64 CpuBackend::Nce, #else diff --git a/src/common/signal_chain.h b/src/common/signal_chain.h index e3bfe68826..8d06a1bd1a 100644 --- a/src/common/signal_chain.h +++ b/src/common/signal_chain.h @@ -10,8 +10,8 @@ namespace Common { // Android's ART overrides sigaction with its own wrapper. This is problematic for SIGSEGV -// in particular, because ARTs handler access TPIDR_EL0, so this extracts the libc version -// and calls it directly. +// in particular, because ART's handler accesses tpidr_el0, which conflicts with NCE. +// This extracts the libc symbol and calls it directly. int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact); } // namespace Common diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 511248a0d8..fd82f3b0e1 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -4,7 +4,6 @@ #include #include -#include "common/scope_exit.h" #include "common/signal_chain.h" #include "core/arm/nce/arm_nce.h" #include "core/arm/nce/patch.h" @@ -32,7 +31,7 @@ static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic); fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) { _aarch64_ctx* header = reinterpret_cast<_aarch64_ctx*>(&host_ctx.__reserved); while (header->magic != FPSIMD_MAGIC) { - header = reinterpret_cast<_aarch64_ctx*>((char*)header + header->size); + header = reinterpret_cast<_aarch64_ctx*>(reinterpret_cast(header) + header->size); } return reinterpret_cast(header); } @@ -124,7 +123,7 @@ bool ARM_NCE::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* ra // 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 which are masking, + // - 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->running_thread->GetNativeExecutionParameters(); diff --git a/src/core/arm/nce/patch.cpp b/src/core/arm/nce/patch.cpp index c79399c2bc..bc4b73634c 100644 --- a/src/core/arm/nce/patch.cpp +++ b/src/core/arm/nce/patch.cpp @@ -100,7 +100,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines) { - const size_t patch_size = SectionSize(); + const size_t patch_size = GetSectionSize(); const size_t image_size = program_image.size(); // Retrieve text segment data. @@ -180,7 +180,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base, } } -size_t Patcher::SectionSize() const noexcept { +size_t Patcher::GetSectionSize() const noexcept { return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE); } @@ -256,7 +256,6 @@ void Patcher::WriteSaveContext() { } void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { - LOG_ERROR(Core_ARM, "Patching SVC {:#x} at {:#x}", svc_id, module_dest - 4); // We are about to start saving state, so we need to lock the context. this->LockContext(); diff --git a/src/core/arm/nce/patch.h b/src/core/arm/nce/patch.h index b727d4e488..dcce1bfc62 100644 --- a/src/core/arm/nce/patch.h +++ b/src/core/arm/nce/patch.h @@ -7,23 +7,17 @@ #include #include -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wshorten-64-to-32" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshorten-64-to-32" #include #include -#pragma clang diagnostic pop +#pragma GCC diagnostic pop #include "common/common_types.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_typed_address.h" #include "core/hle/kernel/physical_memory.h" -#include - -namespace Core { -struct GuestContext; -} - namespace Core::NCE { enum class PatchMode : u32 { @@ -45,9 +39,9 @@ public: const Kernel::CodeSet::Segment& code); void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines); - size_t SectionSize() const noexcept; + size_t GetSectionSize() const noexcept; - [[nodiscard]] PatchMode Mode() const noexcept { + [[nodiscard]] PatchMode GetPatchMode() const noexcept { return mode; } diff --git a/src/core/core.cpp b/src/core/core.cpp index 4084790195..14d6c8c270 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -137,6 +137,7 @@ struct System::Impl { void Initialize(System& system) { device_memory = std::make_unique(); + is_multicore = Settings::values.use_multi_core.GetValue(); extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index f2ffc39c1d..f7f1e8a3bc 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -96,9 +96,11 @@ constexpr Common::MemoryPermission ConvertToMemoryPermission(KMemoryPermission p if (True(perm & KMemoryPermission::UserWrite)) { perms |= Common::MemoryPermission::Write; } +#ifdef ARCHITECTURE_arm64 if (True(perm & KMemoryPermission::UserExecute)) { perms |= Common::MemoryPermission::Execute; } +#endif return perms; } diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 7b97d452b7..e5f796ac7c 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -472,10 +472,6 @@ public: return m_post_handlers; } - KernelCore& GetKernel() noexcept { - return m_kernel; - } - public: // Attempts to insert a watchpoint into a free slot. Returns false if none are available. bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 76ff380418..49d4d7e437 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -204,7 +204,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, #ifdef ARCHITECTURE_arm64 const auto& code = codeset.CodeSegment(); - // NROs are always 64-bit programs. + // NROs always have a 39-bit address space. Settings::SetNceEnabled(true); // Create NCE patcher @@ -215,12 +215,12 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, patch.PatchText(program_image, code); // We only support PostData patching for NROs. - ASSERT(patch.Mode() == Core::NCE::PatchMode::PostData); + ASSERT(patch.GetPatchMode() == Core::NCE::PatchMode::PostData); // Update patch section. auto& patch_segment = codeset.PatchSegment(); patch_segment.addr = image_size; - patch_segment.size = static_cast(patch.SectionSize()); + patch_segment.size = static_cast(patch.GetSectionSize()); // Add patch section size to the module size. image_size += patch_segment.size; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 34b10ef2e4..1ad2e917cd 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -94,8 +94,8 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: // Allocate some space at the beginning if we are patching in PreText mode. const size_t module_start = [&]() -> size_t { #ifdef ARCHITECTURE_arm64 - if (patch && patch->Mode() == Core::NCE::PatchMode::PreText) { - return patch->SectionSize(); + if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) { + return patch->GetSectionSize(); } #endif return 0; @@ -158,24 +158,25 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: #ifdef ARCHITECTURE_arm64 // If we are computing the process code layout and using nce backend, patch. const auto& code = codeset.CodeSegment(); - if (patch && patch->Mode() == Core::NCE::PatchMode::None) { + if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::None) { // Patch SVCs and MRS calls in the guest code patch->PatchText(program_image, code); // Add patch section size to the module size. - image_size += patch->SectionSize(); + image_size += patch->GetSectionSize(); } else if (patch) { // Relocate code patch and copy to the program_image. patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers()); // Update patch section. auto& patch_segment = codeset.PatchSegment(); - patch_segment.addr = patch->Mode() == Core::NCE::PatchMode::PreText ? 0 : image_size; - patch_segment.size = static_cast(patch->SectionSize()); + patch_segment.addr = + patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size; + patch_segment.size = static_cast(patch->GetSectionSize()); // Add patch section size to the module size. In PreText mode image_size // already contains the patch segment as part of module_start. - if (patch->Mode() == Core::NCE::PatchMode::PostData) { + if (patch->GetPatchMode() == Core::NCE::PatchMode::PostData) { image_size += patch_segment.size; } } -- cgit v1.2.3 From 3930e3d7fd5dde092a68543e4a075f28330466d9 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Mon, 20 Nov 2023 15:52:18 +0200 Subject: core: Define HAS_NCE macro --- src/core/CMakeLists.txt | 3 ++- src/core/device_memory.cpp | 2 +- src/core/hle/kernel/code_set.h | 5 +++++ src/core/hle/kernel/k_address_space_info.cpp | 2 +- src/core/hle/kernel/k_page_table_base.cpp | 2 +- src/core/hle/kernel/k_process.h | 6 +++++- src/core/hle/kernel/physical_core.cpp | 4 ++-- src/core/loader/deconstructed_rom_directory.cpp | 6 +++--- src/core/loader/nro.cpp | 6 +++--- src/core/loader/nso.cpp | 6 +++--- 10 files changed, 26 insertions(+), 16 deletions(-) (limited to 'src/core/loader/nso.cpp') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c5805ec618..e0bfb5b955 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -926,7 +926,8 @@ if (ENABLE_WEB_SERVICE) target_link_libraries(core PRIVATE web_service) endif() -if (ARCHITECTURE_arm64) +if (ARCHITECTURE_arm64 AND (ANDROID OR ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")) + target_compile_definitions(core PRIVATE -DHAS_NCE) enable_language(C ASM) set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp index 3a91516469..1aea56a991 100644 --- a/src/core/device_memory.cpp +++ b/src/core/device_memory.cpp @@ -6,7 +6,7 @@ namespace Core { -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE constexpr size_t VirtualReserveSize = 1ULL << 38; #else constexpr size_t VirtualReserveSize = 1ULL << 39; diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h index d53da82f44..4d2d0098e7 100644 --- a/src/core/hle/kernel/code_set.h +++ b/src/core/hle/kernel/code_set.h @@ -75,6 +75,7 @@ struct CodeSet final { return segments[2]; } +#ifdef HAS_NCE Segment& PatchSegment() { return patch_segment; } @@ -82,13 +83,17 @@ struct CodeSet final { const Segment& PatchSegment() const { return patch_segment; } +#endif /// The overall data that backs this code set. Kernel::PhysicalMemory memory; /// The segments that comprise this code set. std::array segments; + +#ifdef HAS_NCE Segment patch_segment; +#endif /// The entry point address for this code set. KProcessAddress entrypoint = 0; diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp index 3235a7a376..23258071e1 100644 --- a/src/core/hle/kernel/k_address_space_info.cpp +++ b/src/core/hle/kernel/k_address_space_info.cpp @@ -25,7 +25,7 @@ constexpr std::array AddressSpaceInfos{{ { .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, }, { .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, }, { .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, }, -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE // With NCE, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region. { .bit_width = 39, .address = 128_MiB , .size = 256_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, }, #else diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index f7f1e8a3bc..2b5e77ccf3 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -96,7 +96,7 @@ constexpr Common::MemoryPermission ConvertToMemoryPermission(KMemoryPermission p if (True(perm & KMemoryPermission::UserWrite)) { perms |= Common::MemoryPermission::Write; } -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE if (True(perm & KMemoryPermission::UserExecute)) { perms |= Common::MemoryPermission::Execute; } diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index e5f796ac7c..d8cd0fdde7 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -112,7 +112,6 @@ private: std::array m_pinned_threads{}; std::array m_watchpoints{}; std::map m_debug_page_refcounts{}; - std::unordered_map m_post_handlers{}; std::atomic m_cpu_time{}; std::atomic m_num_process_switches{}; std::atomic m_num_thread_switches{}; @@ -121,6 +120,9 @@ private: std::atomic m_num_ipc_messages{}; std::atomic m_num_ipc_replies{}; std::atomic m_num_ipc_receives{}; +#ifdef HAS_NCE + std::unordered_map m_post_handlers{}; +#endif private: Result StartTermination(); @@ -468,9 +470,11 @@ public: static void Switch(KProcess* cur_process, KProcess* next_process); +#ifdef HAS_NCE std::unordered_map& GetPostHandlers() noexcept { return m_post_handlers; } +#endif public: // Attempts to insert a watchpoint into a free slot. Returns false if none are available. diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index 15434212e7..0730398255 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -4,7 +4,7 @@ #include "common/settings.h" #include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_64.h" -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE #include "core/arm/nce/arm_nce.h" #endif #include "core/core.h" @@ -33,7 +33,7 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu PhysicalCore::~PhysicalCore() = default; void PhysicalCore::Initialize(bool is_64_bit) { -#if defined(ARCHITECTURE_arm64) +#if defined(HAS_NCE) if (Settings::IsNceEnabled()) { m_arm_interface = std::make_unique(m_system, m_system.Kernel().IsMulticore(), m_core_index); diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 31c00f0a31..a1344f1c41 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -15,7 +15,7 @@ #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/nso.h" -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE #include "core/arm/nce/patch.h" #endif @@ -141,12 +141,12 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect std::size_t code_size{}; // Define an nce patch context for each potential module. -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE std::array module_patchers; #endif const auto GetPatcher = [&](size_t i) -> Core::NCE::Patcher* { -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE if (Settings::IsNceEnabled()) { return &module_patchers[i]; } diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 49d4d7e437..2e73683495 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -22,7 +22,7 @@ #include "core/loader/nso.h" #include "core/memory.h" -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE #include "core/arm/nce/patch.h" #endif @@ -201,7 +201,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, program_image.resize(static_cast(program_image.size()) + bss_size); size_t image_size = program_image.size(); -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE const auto& code = codeset.CodeSegment(); // NROs always have a 39-bit address space. @@ -247,7 +247,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, // Relocate code patch and copy to the program_image if running under NCE. // This needs to be after LoadFromMetadata so we can use the process entry point. -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE if (Settings::IsNceEnabled()) { patch.RelocateAndCopy(process.GetEntryPoint(), code, program_image, &process.GetPostHandlers()); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 1ad2e917cd..878c1c6cb1 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -20,7 +20,7 @@ #include "core/loader/nso.h" #include "core/memory.h" -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE #include "core/arm/nce/patch.h" #endif @@ -93,7 +93,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: // Allocate some space at the beginning if we are patching in PreText mode. const size_t module_start = [&]() -> size_t { -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) { return patch->GetSectionSize(); } @@ -155,7 +155,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); } -#ifdef ARCHITECTURE_arm64 +#ifdef HAS_NCE // If we are computing the process code layout and using nce backend, patch. const auto& code = codeset.CodeSegment(); if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::None) { -- cgit v1.2.3 From 982183d4c67215996325c89b8baa24a3abe26aa7 Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 21 Nov 2023 23:29:40 -0500 Subject: loader: apply nso patch to offset program image --- src/core/loader/nso.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/core/loader/nso.cpp') diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 878c1c6cb1..e310c8f0fa 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -145,14 +145,16 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: // Apply patches if necessary const auto name = nso_file.GetName(); if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) { - std::vector pi_header(sizeof(NSOHeader) + program_image.size()); + std::span patchable_section(program_image.data() + module_start, + program_image.size() - module_start); + std::vector pi_header(sizeof(NSOHeader) + patchable_section.size()); std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); - std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), - program_image.size()); + std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(), + patchable_section.size()); pi_header = pm->PatchNSO(pi_header, name); - std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); + std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), patchable_section.data()); } #ifdef HAS_NCE -- cgit v1.2.3 From d7db22185ab3241b628a128f01019caf5789c659 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 26 Nov 2023 19:34:00 -0500 Subject: loader: fix gcc compile --- src/core/loader/nso.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/core/loader/nso.cpp') diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index e310c8f0fa..ade8f85f89 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -165,7 +165,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: patch->PatchText(program_image, code); // Add patch section size to the module size. - image_size += patch->GetSectionSize(); + image_size += static_cast(patch->GetSectionSize()); } else if (patch) { // Relocate code patch and copy to the program_image. patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers()); -- cgit v1.2.3 From e6e15d2d108edad44f5a77395ac837809638c891 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Wed, 29 Nov 2023 23:49:16 +0200 Subject: core: Rename patcher file --- src/core/CMakeLists.txt | 4 +- src/core/arm/nce/arm_nce.cpp | 2 +- src/core/arm/nce/patch.cpp | 474 ------------------------ src/core/arm/nce/patch.h | 98 ----- src/core/arm/nce/patcher.cpp | 474 ++++++++++++++++++++++++ src/core/arm/nce/patcher.h | 98 +++++ src/core/loader/deconstructed_rom_directory.cpp | 2 +- src/core/loader/nro.cpp | 2 +- src/core/loader/nso.cpp | 2 +- 9 files changed, 578 insertions(+), 578 deletions(-) delete mode 100644 src/core/arm/nce/patch.cpp delete mode 100644 src/core/arm/nce/patch.h create mode 100644 src/core/arm/nce/patcher.cpp create mode 100644 src/core/arm/nce/patcher.h (limited to 'src/core/loader/nso.cpp') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c3ab5ecf99..85583941cb 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -935,8 +935,8 @@ if (HAS_NCE) arm/nce/arm_nce.h arm/nce/arm_nce.s arm/nce/guest_context.h - arm/nce/patch.cpp - arm/nce/patch.h + arm/nce/patcher.cpp + arm/nce/patcher.h arm/nce/instructions.h ) target_link_libraries(core PRIVATE merry::oaknut) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 6eb6299cc9..f7bdafd396 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -6,7 +6,7 @@ #include "common/signal_chain.h" #include "core/arm/nce/arm_nce.h" -#include "core/arm/nce/patch.h" +#include "core/arm/nce/patcher.h" #include "core/core.h" #include "core/memory.h" diff --git a/src/core/arm/nce/patch.cpp b/src/core/arm/nce/patch.cpp deleted file mode 100644 index a08859d0b1..0000000000 --- a/src/core/arm/nce/patch.cpp +++ /dev/null @@ -1,474 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/arm64/native_clock.h" -#include "common/bit_cast.h" -#include "common/literals.h" -#include "core/arm/nce/arm_nce.h" -#include "core/arm/nce/guest_context.h" -#include "core/arm/nce/instructions.h" -#include "core/arm/nce/patch.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/hle/kernel/svc.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); - -Patcher::Patcher() : c(m_patch_instructions) {} - -Patcher::~Patcher() = default; - -void Patcher::PatchText(const Kernel::PhysicalMemory& program_image, - const Kernel::CodeSet::Segment& code) { - - // Write save context helper function. - c.l(m_save_context); - WriteSaveContext(); - - // Write load context helper function. - c.l(m_load_context); - WriteLoadContext(); - - // Retrieve text segment data. - const auto text = std::span{program_image}.subspan(code.offset, code.size); - const auto text_words = - std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; - - // Loop through instructions, patching as needed. - for (u32 i = ModuleCodeIndex; i < static_cast(text_words.size()); i++) { - const u32 inst = text_words[i]; - - const auto AddRelocations = [&] { - const uintptr_t this_offset = i * sizeof(u32); - const uintptr_t next_offset = this_offset + sizeof(u32); - - // Relocate from here to patch. - this->BranchToPatch(this_offset); - - // Relocate from patch to next instruction. - return next_offset; - }; - - // SVC - if (auto svc = SVC{inst}; svc.Verify()) { - WriteSvcTrampoline(AddRelocations(), svc.GetValue()); - continue; - } - - // MRS Xn, TPIDR_EL0 - // MRS Xn, TPIDRRO_EL0 - if (auto mrs = MRS{inst}; - mrs.Verify() && (mrs.GetSystemReg() == TpidrroEl0 || mrs.GetSystemReg() == TpidrEl0)) { - const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0 - : oaknut::SystemReg::TPIDR_EL0; - const auto dest_reg = oaknut::XReg{static_cast(mrs.GetRt())}; - WriteMrsHandler(AddRelocations(), dest_reg, src_reg); - continue; - } - - // MRS Xn, CNTPCT_EL0 - if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) { - WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast(mrs.GetRt())}); - continue; - } - - // MRS Xn, CNTFRQ_EL0 - if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) { - UNREACHABLE(); - } - - // MSR TPIDR_EL0, Xn - if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) { - WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast(msr.GetRt())}); - continue; - } - - if (auto exclusive = Exclusive{inst}; exclusive.Verify()) { - m_exclusives.push_back(i); - } - } - - // Determine patching mode for the final relocation step - const size_t image_size = program_image.size(); - this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData; -} - -void Patcher::RelocateAndCopy(Common::ProcessAddress load_base, - const Kernel::CodeSet::Segment& code, - Kernel::PhysicalMemory& program_image, - EntryTrampolines* out_trampolines) { - const size_t patch_size = GetSectionSize(); - const size_t image_size = program_image.size(); - - // Retrieve text segment data. - const auto text = std::span{program_image}.subspan(code.offset, code.size); - const auto text_words = - std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; - - const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) { - oaknut::CodeGenerator rc{target}; - if (mode == PatchMode::PreText) { - rc.B(rel.patch_offset - patch_size - rel.module_offset); - } else { - rc.B(image_size - rel.module_offset + rel.patch_offset); - } - }; - - const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) { - oaknut::CodeGenerator rc{target}; - if (mode == PatchMode::PreText) { - rc.B(patch_size - rel.patch_offset + rel.module_offset); - } else { - rc.B(rel.module_offset - image_size - rel.patch_offset); - } - }; - - const auto RebasePatch = [&](ptrdiff_t patch_offset) { - if (mode == PatchMode::PreText) { - return GetInteger(load_base) + patch_offset; - } else { - return GetInteger(load_base) + image_size + patch_offset; - } - }; - - const auto RebasePc = [&](uintptr_t module_offset) { - if (mode == PatchMode::PreText) { - return GetInteger(load_base) + patch_size + module_offset; - } else { - return GetInteger(load_base) + module_offset; - } - }; - - // We are now ready to relocate! - for (const Relocation& rel : m_branch_to_patch_relocations) { - ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel); - } - for (const Relocation& rel : m_branch_to_module_relocations) { - ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), - rel); - } - - // Rewrite PC constants and record post trampolines - for (const Relocation& rel : m_write_module_pc_relocations) { - oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; - rc.dx(RebasePc(rel.module_offset)); - } - for (const Trampoline& rel : m_trampolines) { - out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)}); - } - - // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not. - // Convert to ordered to preserve this assumption. - for (const ModuleTextAddress i : m_exclusives) { - auto exclusive = Exclusive{text_words[i]}; - text_words[i] = exclusive.AsOrdered(); - } - - // Copy to program image - if (this->mode == PatchMode::PreText) { - std::memcpy(program_image.data(), 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(), - m_patch_instructions.size() * sizeof(u32)); - } -} - -size_t Patcher::GetSectionSize() const noexcept { - return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE); -} - -void Patcher::WriteLoadContext() { - // This function was called, which modifies X30, so use that as a scratch register. - // SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes - // of stack. - c.STR(X30, SP, 8); - c.MRS(X30, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); - - // Load system registers. - c.LDR(W0, X30, offsetof(GuestContext, fpsr)); - c.MSR(oaknut::SystemReg::FPSR, X0); - c.LDR(W0, X30, offsetof(GuestContext, fpcr)); - c.MSR(oaknut::SystemReg::FPCR, X0); - c.LDR(W0, X30, offsetof(GuestContext, nzcv)); - c.MSR(oaknut::SystemReg::NZCV, X0); - - // Load all vector registers. - static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers); - for (int i = 0; i <= 30; i += 2) { - c.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); - } - - // Load all general-purpose registers except X30. - for (int i = 0; i <= 28; i += 2) { - c.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); - } - - // Reload our return X30 from the stack and return. - // The patch code will reload the guest X30 for us. - c.LDR(X30, SP, 8); - c.RET(); -} - -void Patcher::WriteSaveContext() { - // This function was called, which modifies X30, so use that as a scratch register. - // SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of - // stack. - c.STR(X30, SP, 8); - c.MRS(X30, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); - - // Store all general-purpose registers except X30. - for (int i = 0; i <= 28; i += 2) { - c.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); - } - - // Store all vector registers. - static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers); - for (int i = 0; i <= 30; i += 2) { - c.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); - } - - // Store guest system registers, X30 and SP, using X0 as a scratch register. - c.STR(X0, SP, PRE_INDEXED, -16); - c.LDR(X0, SP, 16); - c.STR(X0, X30, 8 * 30); - c.ADD(X0, SP, 32); - c.STR(X0, X30, offsetof(GuestContext, sp)); - c.MRS(X0, oaknut::SystemReg::FPSR); - c.STR(W0, X30, offsetof(GuestContext, fpsr)); - c.MRS(X0, oaknut::SystemReg::FPCR); - c.STR(W0, X30, offsetof(GuestContext, fpcr)); - c.MRS(X0, oaknut::SystemReg::NZCV); - c.STR(W0, X30, offsetof(GuestContext, nzcv)); - c.LDR(X0, SP, POST_INDEXED, 16); - - // Reload our return X30 from the stack, and return. - c.LDR(X30, SP, 8); - c.RET(); -} - -void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { - // We are about to start saving state, so we need to lock the context. - this->LockContext(); - - // Store guest X30 to the stack. Then, save the context and restore the stack. - // This will save all registers except PC, but we know PC at patch time. - c.STR(X30, SP, PRE_INDEXED, -16); - c.BL(m_save_context); - c.LDR(X30, SP, POST_INDEXED, 16); - - // Now that we've saved all registers, we can use any registers as scratch. - // Store PC + 4 to arm interface, since we know the instruction offset from the entry point. - oaknut::Label pc_after_svc; - c.MRS(X1, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); - c.LDR(X2, pc_after_svc); - c.STR(X2, X1, offsetof(GuestContext, pc)); - - // Store SVC number to execute when we return - c.MOV(X2, svc_id); - c.STR(W2, X1, offsetof(GuestContext, svc_swi)); - - // We are calling a SVC. Clear esr_el1 and return it. - static_assert(std::is_same_v, u64>); - oaknut::Label retry; - c.ADD(X2, X1, offsetof(GuestContext, esr_el1)); - c.l(retry); - c.LDAXR(X0, X2); - c.STLXR(W3, XZR, X2); - 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)); - - // Offset the GuestContext pointer to the HostContext member. - // STP has limited range of [-512, 504] which we can't reach otherwise - // NB: Due to this all offsets below are from the start of HostContext. - c.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)); - c.LDP(X2, X3, X1, offsetof(HostContext, host_sp)); - c.MOV(SP, X2); - c.MSR(oaknut::SystemReg::TPIDR_EL0, X3); - - // Load callee-saved host registers and return to host. - static constexpr size_t HOST_REGS_OFF = offsetof(HostContext, host_saved_regs); - static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs); - c.LDP(X19, X20, X1, HOST_REGS_OFF); - c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64)); - c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64)); - c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64)); - c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64)); - c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64)); - c.LDP(Q8, Q9, X1, HOST_VREGS_OFF); - c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128)); - c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128)); - c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128)); - c.RET(); - - // Write the post-SVC trampoline address, which will jump back to the guest after restoring its - // state. - m_trampolines.push_back({c.offset(), module_dest}); - - // Host called this location. Save the return address so we can - // unwind the stack properly when jumping back. - c.MRS(X2, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context)); - c.ADD(X0, X2, offsetof(GuestContext, host_ctx)); - c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64)); - - // Reload all guest registers except X30 and PC. - // The function also expects 16 bytes of stack already allocated. - c.STR(X30, SP, PRE_INDEXED, -16); - c.BL(m_load_context); - c.LDR(X30, SP, POST_INDEXED, 16); - - // Use X1 as a scratch register to restore X30. - c.STR(X1, SP, PRE_INDEXED, -16); - c.MRS(X1, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); - c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30); - c.LDR(X1, SP, POST_INDEXED, 16); - - // Unlock the context. - this->UnlockContext(); - - // Jump back to the instruction after the emulated SVC. - this->BranchToModule(module_dest); - - // Store PC after call. - c.l(pc_after_svc); - this->WriteModulePc(module_dest); -} - -void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, - oaknut::SystemReg src_reg) { - // Retrieve emulated TLS register from GuestContext. - c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0); - if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) { - c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0)); - } else { - c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0)); - } - - // Jump back to the instruction after the emulated MRS. - this->BranchToModule(module_dest); -} - -void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { - const auto scratch_reg = src_reg.index() == 0 ? X1 : X0; - c.STR(scratch_reg, SP, PRE_INDEXED, -16); - - // Save guest value to NativeExecutionParameters::tpidr_el0. - c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0); - c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0)); - - // Restore scratch register. - c.LDR(scratch_reg, SP, POST_INDEXED, 16); - - // Jump back to the instruction after the emulated MSR. - this->BranchToModule(module_dest); -} - -void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { - static Common::Arm64::NativeClock clock{}; - const auto factor = clock.GetGuestCNTFRQFactor(); - const auto raw_factor = Common::BitCast>(factor); - - const auto use_x2_x3 = dest_reg.index() == 0 || dest_reg.index() == 1; - oaknut::XReg scratch0 = use_x2_x3 ? X2 : X0; - oaknut::XReg scratch1 = use_x2_x3 ? X3 : X1; - - oaknut::Label factorlo; - oaknut::Label factorhi; - - // Save scratches. - c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16); - - // Load counter value. - c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0); - - // Load scaling factor. - c.LDR(scratch0, factorlo); - c.LDR(scratch1, factorhi); - - // Multiply low bits and get result. - c.UMULH(scratch0, dest_reg, scratch0); - - // Multiply high bits and add low bit result. - c.MADD(dest_reg, dest_reg, scratch1, scratch0); - - // Reload scratches. - c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16); - - // Jump back to the instruction after the emulated MRS. - this->BranchToModule(module_dest); - - // Scaling factor constant values. - c.l(factorlo); - c.dx(raw_factor[0]); - c.l(factorhi); - c.dx(raw_factor[1]); -} - -void Patcher::LockContext() { - oaknut::Label retry; - - // Save scratches. - c.STP(X0, X1, SP, PRE_INDEXED, -16); - - // Reload lock pointer. - c.l(retry); - c.CLREX(); - c.MRS(X0, oaknut::SystemReg::TPIDR_EL0); - c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); - - static_assert(SpinLockLocked == 0); - - // Load-linked with acquire ordering. - c.LDAXR(W1, X0); - - // If the value was SpinLockLocked, clear monitor and retry. - c.CBZ(W1, retry); - - // Store-conditional SpinLockLocked with relaxed ordering. - c.STXR(W1, WZR, X0); - - // If we failed to store, retry. - c.CBNZ(W1, retry); - - // We succeeded! Reload scratches. - c.LDP(X0, X1, SP, POST_INDEXED, 16); -} - -void Patcher::UnlockContext() { - // Save scratches. - c.STP(X0, X1, SP, PRE_INDEXED, -16); - - // Load lock pointer. - c.MRS(X0, oaknut::SystemReg::TPIDR_EL0); - c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); - - // Load SpinLockUnlocked. - c.MOV(W1, SpinLockUnlocked); - - // Store value with release ordering. - c.STLR(W1, X0); - - // Load scratches. - c.LDP(X0, X1, SP, POST_INDEXED, 16); -} - -} // namespace Core::NCE diff --git a/src/core/arm/nce/patch.h b/src/core/arm/nce/patch.h deleted file mode 100644 index c6d1608c18..0000000000 --- a/src/core/arm/nce/patch.h +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include "common/common_types.h" -#include "core/hle/kernel/code_set.h" -#include "core/hle/kernel/k_typed_address.h" -#include "core/hle/kernel/physical_memory.h" - -namespace Core::NCE { - -enum class PatchMode : u32 { - None, - PreText, ///< Patch section is inserted before .text - PostData, ///< Patch section is inserted after .data -}; - -using ModuleTextAddress = u64; -using PatchTextAddress = u64; -using EntryTrampolines = std::unordered_map; - -class Patcher { -public: - explicit Patcher(); - ~Patcher(); - - void PatchText(const Kernel::PhysicalMemory& program_image, - const Kernel::CodeSet::Segment& code); - void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, - Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines); - size_t GetSectionSize() const noexcept; - - [[nodiscard]] PatchMode GetPatchMode() const noexcept { - return mode; - } - -private: - using ModuleDestLabel = uintptr_t; - - struct Trampoline { - ptrdiff_t patch_offset; - uintptr_t module_offset; - }; - - void WriteLoadContext(); - void WriteSaveContext(); - void LockContext(); - void UnlockContext(); - void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id); - void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, - oaknut::SystemReg src_reg); - void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg); - void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg); - -private: - void BranchToPatch(uintptr_t module_dest) { - m_branch_to_patch_relocations.push_back({c.offset(), module_dest}); - } - - void BranchToModule(uintptr_t module_dest) { - m_branch_to_module_relocations.push_back({c.offset(), module_dest}); - c.dw(0); - } - - void WriteModulePc(uintptr_t module_dest) { - m_write_module_pc_relocations.push_back({c.offset(), module_dest}); - c.dx(0); - } - -private: - // List of patch instructions we have generated. - std::vector m_patch_instructions{}; - - // Relocation type for relative branch from module to patch. - struct Relocation { - ptrdiff_t patch_offset; ///< Offset in bytes from the start of the patch section. - uintptr_t module_offset; ///< Offset in bytes from the start of the text section. - }; - - oaknut::VectorCodeGenerator c; - std::vector m_trampolines; - std::vector m_branch_to_patch_relocations{}; - std::vector m_branch_to_module_relocations{}; - std::vector m_write_module_pc_relocations{}; - std::vector m_exclusives{}; - oaknut::Label m_save_context{}; - oaknut::Label m_load_context{}; - PatchMode mode{PatchMode::None}; -}; - -} // namespace Core::NCE diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp new file mode 100644 index 0000000000..ec8527224a --- /dev/null +++ b/src/core/arm/nce/patcher.cpp @@ -0,0 +1,474 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/arm64/native_clock.h" +#include "common/bit_cast.h" +#include "common/literals.h" +#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" + +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); + +Patcher::Patcher() : c(m_patch_instructions) {} + +Patcher::~Patcher() = default; + +void Patcher::PatchText(const Kernel::PhysicalMemory& program_image, + const Kernel::CodeSet::Segment& code) { + + // Write save context helper function. + c.l(m_save_context); + WriteSaveContext(); + + // Write load context helper function. + c.l(m_load_context); + WriteLoadContext(); + + // Retrieve text segment data. + const auto text = std::span{program_image}.subspan(code.offset, code.size); + const auto text_words = + std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; + + // Loop through instructions, patching as needed. + for (u32 i = ModuleCodeIndex; i < static_cast(text_words.size()); i++) { + const u32 inst = text_words[i]; + + const auto AddRelocations = [&] { + const uintptr_t this_offset = i * sizeof(u32); + const uintptr_t next_offset = this_offset + sizeof(u32); + + // Relocate from here to patch. + this->BranchToPatch(this_offset); + + // Relocate from patch to next instruction. + return next_offset; + }; + + // SVC + if (auto svc = SVC{inst}; svc.Verify()) { + WriteSvcTrampoline(AddRelocations(), svc.GetValue()); + continue; + } + + // MRS Xn, TPIDR_EL0 + // MRS Xn, TPIDRRO_EL0 + if (auto mrs = MRS{inst}; + mrs.Verify() && (mrs.GetSystemReg() == TpidrroEl0 || mrs.GetSystemReg() == TpidrEl0)) { + const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0 + : oaknut::SystemReg::TPIDR_EL0; + const auto dest_reg = oaknut::XReg{static_cast(mrs.GetRt())}; + WriteMrsHandler(AddRelocations(), dest_reg, src_reg); + continue; + } + + // MRS Xn, CNTPCT_EL0 + if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) { + WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast(mrs.GetRt())}); + continue; + } + + // MRS Xn, CNTFRQ_EL0 + if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) { + UNREACHABLE(); + } + + // MSR TPIDR_EL0, Xn + if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) { + WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast(msr.GetRt())}); + continue; + } + + if (auto exclusive = Exclusive{inst}; exclusive.Verify()) { + m_exclusives.push_back(i); + } + } + + // Determine patching mode for the final relocation step + const size_t image_size = program_image.size(); + this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData; +} + +void Patcher::RelocateAndCopy(Common::ProcessAddress load_base, + const Kernel::CodeSet::Segment& code, + Kernel::PhysicalMemory& program_image, + EntryTrampolines* out_trampolines) { + const size_t patch_size = GetSectionSize(); + const size_t image_size = program_image.size(); + + // Retrieve text segment data. + const auto text = std::span{program_image}.subspan(code.offset, code.size); + const auto text_words = + std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; + + const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) { + oaknut::CodeGenerator rc{target}; + if (mode == PatchMode::PreText) { + rc.B(rel.patch_offset - patch_size - rel.module_offset); + } else { + rc.B(image_size - rel.module_offset + rel.patch_offset); + } + }; + + const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) { + oaknut::CodeGenerator rc{target}; + if (mode == PatchMode::PreText) { + rc.B(patch_size - rel.patch_offset + rel.module_offset); + } else { + rc.B(rel.module_offset - image_size - rel.patch_offset); + } + }; + + const auto RebasePatch = [&](ptrdiff_t patch_offset) { + if (mode == PatchMode::PreText) { + return GetInteger(load_base) + patch_offset; + } else { + return GetInteger(load_base) + image_size + patch_offset; + } + }; + + const auto RebasePc = [&](uintptr_t module_offset) { + if (mode == PatchMode::PreText) { + return GetInteger(load_base) + patch_size + module_offset; + } else { + return GetInteger(load_base) + module_offset; + } + }; + + // We are now ready to relocate! + for (const Relocation& rel : m_branch_to_patch_relocations) { + ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel); + } + for (const Relocation& rel : m_branch_to_module_relocations) { + ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), + rel); + } + + // Rewrite PC constants and record post trampolines + for (const Relocation& rel : m_write_module_pc_relocations) { + oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; + rc.dx(RebasePc(rel.module_offset)); + } + for (const Trampoline& rel : m_trampolines) { + out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)}); + } + + // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not. + // Convert to ordered to preserve this assumption. + for (const ModuleTextAddress i : m_exclusives) { + auto exclusive = Exclusive{text_words[i]}; + text_words[i] = exclusive.AsOrdered(); + } + + // Copy to program image + if (this->mode == PatchMode::PreText) { + std::memcpy(program_image.data(), 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(), + m_patch_instructions.size() * sizeof(u32)); + } +} + +size_t Patcher::GetSectionSize() const noexcept { + return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE); +} + +void Patcher::WriteLoadContext() { + // This function was called, which modifies X30, so use that as a scratch register. + // SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes + // of stack. + c.STR(X30, SP, 8); + c.MRS(X30, oaknut::SystemReg::TPIDR_EL0); + c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); + + // Load system registers. + c.LDR(W0, X30, offsetof(GuestContext, fpsr)); + c.MSR(oaknut::SystemReg::FPSR, X0); + c.LDR(W0, X30, offsetof(GuestContext, fpcr)); + c.MSR(oaknut::SystemReg::FPCR, X0); + c.LDR(W0, X30, offsetof(GuestContext, nzcv)); + c.MSR(oaknut::SystemReg::NZCV, X0); + + // Load all vector registers. + static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers); + for (int i = 0; i <= 30; i += 2) { + c.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); + } + + // Load all general-purpose registers except X30. + for (int i = 0; i <= 28; i += 2) { + c.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); + } + + // Reload our return X30 from the stack and return. + // The patch code will reload the guest X30 for us. + c.LDR(X30, SP, 8); + c.RET(); +} + +void Patcher::WriteSaveContext() { + // This function was called, which modifies X30, so use that as a scratch register. + // SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of + // stack. + c.STR(X30, SP, 8); + c.MRS(X30, oaknut::SystemReg::TPIDR_EL0); + c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); + + // Store all general-purpose registers except X30. + for (int i = 0; i <= 28; i += 2) { + c.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); + } + + // Store all vector registers. + static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers); + for (int i = 0; i <= 30; i += 2) { + c.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); + } + + // Store guest system registers, X30 and SP, using X0 as a scratch register. + c.STR(X0, SP, PRE_INDEXED, -16); + c.LDR(X0, SP, 16); + c.STR(X0, X30, 8 * 30); + c.ADD(X0, SP, 32); + c.STR(X0, X30, offsetof(GuestContext, sp)); + c.MRS(X0, oaknut::SystemReg::FPSR); + c.STR(W0, X30, offsetof(GuestContext, fpsr)); + c.MRS(X0, oaknut::SystemReg::FPCR); + c.STR(W0, X30, offsetof(GuestContext, fpcr)); + c.MRS(X0, oaknut::SystemReg::NZCV); + c.STR(W0, X30, offsetof(GuestContext, nzcv)); + c.LDR(X0, SP, POST_INDEXED, 16); + + // Reload our return X30 from the stack, and return. + c.LDR(X30, SP, 8); + c.RET(); +} + +void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { + // We are about to start saving state, so we need to lock the context. + this->LockContext(); + + // Store guest X30 to the stack. Then, save the context and restore the stack. + // This will save all registers except PC, but we know PC at patch time. + c.STR(X30, SP, PRE_INDEXED, -16); + c.BL(m_save_context); + c.LDR(X30, SP, POST_INDEXED, 16); + + // Now that we've saved all registers, we can use any registers as scratch. + // Store PC + 4 to arm interface, since we know the instruction offset from the entry point. + oaknut::Label pc_after_svc; + c.MRS(X1, oaknut::SystemReg::TPIDR_EL0); + c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); + c.LDR(X2, pc_after_svc); + c.STR(X2, X1, offsetof(GuestContext, pc)); + + // Store SVC number to execute when we return + c.MOV(X2, svc_id); + c.STR(W2, X1, offsetof(GuestContext, svc_swi)); + + // We are calling a SVC. Clear esr_el1 and return it. + static_assert(std::is_same_v, u64>); + oaknut::Label retry; + c.ADD(X2, X1, offsetof(GuestContext, esr_el1)); + c.l(retry); + c.LDAXR(X0, X2); + c.STLXR(W3, XZR, X2); + 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)); + + // Offset the GuestContext pointer to the HostContext member. + // STP has limited range of [-512, 504] which we can't reach otherwise + // NB: Due to this all offsets below are from the start of HostContext. + c.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)); + c.LDP(X2, X3, X1, offsetof(HostContext, host_sp)); + c.MOV(SP, X2); + c.MSR(oaknut::SystemReg::TPIDR_EL0, X3); + + // Load callee-saved host registers and return to host. + static constexpr size_t HOST_REGS_OFF = offsetof(HostContext, host_saved_regs); + static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs); + c.LDP(X19, X20, X1, HOST_REGS_OFF); + c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64)); + c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64)); + c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64)); + c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64)); + c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64)); + c.LDP(Q8, Q9, X1, HOST_VREGS_OFF); + c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128)); + c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128)); + c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128)); + c.RET(); + + // Write the post-SVC trampoline address, which will jump back to the guest after restoring its + // state. + m_trampolines.push_back({c.offset(), module_dest}); + + // Host called this location. Save the return address so we can + // unwind the stack properly when jumping back. + c.MRS(X2, oaknut::SystemReg::TPIDR_EL0); + c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context)); + c.ADD(X0, X2, offsetof(GuestContext, host_ctx)); + c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64)); + + // Reload all guest registers except X30 and PC. + // The function also expects 16 bytes of stack already allocated. + c.STR(X30, SP, PRE_INDEXED, -16); + c.BL(m_load_context); + c.LDR(X30, SP, POST_INDEXED, 16); + + // Use X1 as a scratch register to restore X30. + c.STR(X1, SP, PRE_INDEXED, -16); + c.MRS(X1, oaknut::SystemReg::TPIDR_EL0); + c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); + c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30); + c.LDR(X1, SP, POST_INDEXED, 16); + + // Unlock the context. + this->UnlockContext(); + + // Jump back to the instruction after the emulated SVC. + this->BranchToModule(module_dest); + + // Store PC after call. + c.l(pc_after_svc); + this->WriteModulePc(module_dest); +} + +void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, + oaknut::SystemReg src_reg) { + // Retrieve emulated TLS register from GuestContext. + c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0); + if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) { + c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0)); + } else { + c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0)); + } + + // Jump back to the instruction after the emulated MRS. + this->BranchToModule(module_dest); +} + +void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { + const auto scratch_reg = src_reg.index() == 0 ? X1 : X0; + c.STR(scratch_reg, SP, PRE_INDEXED, -16); + + // Save guest value to NativeExecutionParameters::tpidr_el0. + c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0); + c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0)); + + // Restore scratch register. + c.LDR(scratch_reg, SP, POST_INDEXED, 16); + + // Jump back to the instruction after the emulated MSR. + this->BranchToModule(module_dest); +} + +void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { + static Common::Arm64::NativeClock clock{}; + const auto factor = clock.GetGuestCNTFRQFactor(); + const auto raw_factor = Common::BitCast>(factor); + + const auto use_x2_x3 = dest_reg.index() == 0 || dest_reg.index() == 1; + oaknut::XReg scratch0 = use_x2_x3 ? X2 : X0; + oaknut::XReg scratch1 = use_x2_x3 ? X3 : X1; + + oaknut::Label factorlo; + oaknut::Label factorhi; + + // Save scratches. + c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16); + + // Load counter value. + c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0); + + // Load scaling factor. + c.LDR(scratch0, factorlo); + c.LDR(scratch1, factorhi); + + // Multiply low bits and get result. + c.UMULH(scratch0, dest_reg, scratch0); + + // Multiply high bits and add low bit result. + c.MADD(dest_reg, dest_reg, scratch1, scratch0); + + // Reload scratches. + c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16); + + // Jump back to the instruction after the emulated MRS. + this->BranchToModule(module_dest); + + // Scaling factor constant values. + c.l(factorlo); + c.dx(raw_factor[0]); + c.l(factorhi); + c.dx(raw_factor[1]); +} + +void Patcher::LockContext() { + oaknut::Label retry; + + // Save scratches. + c.STP(X0, X1, SP, PRE_INDEXED, -16); + + // Reload lock pointer. + c.l(retry); + c.CLREX(); + c.MRS(X0, oaknut::SystemReg::TPIDR_EL0); + c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); + + static_assert(SpinLockLocked == 0); + + // Load-linked with acquire ordering. + c.LDAXR(W1, X0); + + // If the value was SpinLockLocked, clear monitor and retry. + c.CBZ(W1, retry); + + // Store-conditional SpinLockLocked with relaxed ordering. + c.STXR(W1, WZR, X0); + + // If we failed to store, retry. + c.CBNZ(W1, retry); + + // We succeeded! Reload scratches. + c.LDP(X0, X1, SP, POST_INDEXED, 16); +} + +void Patcher::UnlockContext() { + // Save scratches. + c.STP(X0, X1, SP, PRE_INDEXED, -16); + + // Load lock pointer. + c.MRS(X0, oaknut::SystemReg::TPIDR_EL0); + c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); + + // Load SpinLockUnlocked. + c.MOV(W1, SpinLockUnlocked); + + // Store value with release ordering. + c.STLR(W1, X0); + + // Load scratches. + c.LDP(X0, X1, SP, POST_INDEXED, 16); +} + +} // namespace Core::NCE diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h new file mode 100644 index 0000000000..c6d1608c18 --- /dev/null +++ b/src/core/arm/nce/patcher.h @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/common_types.h" +#include "core/hle/kernel/code_set.h" +#include "core/hle/kernel/k_typed_address.h" +#include "core/hle/kernel/physical_memory.h" + +namespace Core::NCE { + +enum class PatchMode : u32 { + None, + PreText, ///< Patch section is inserted before .text + PostData, ///< Patch section is inserted after .data +}; + +using ModuleTextAddress = u64; +using PatchTextAddress = u64; +using EntryTrampolines = std::unordered_map; + +class Patcher { +public: + explicit Patcher(); + ~Patcher(); + + void PatchText(const Kernel::PhysicalMemory& program_image, + const Kernel::CodeSet::Segment& code); + void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, + Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines); + size_t GetSectionSize() const noexcept; + + [[nodiscard]] PatchMode GetPatchMode() const noexcept { + return mode; + } + +private: + using ModuleDestLabel = uintptr_t; + + struct Trampoline { + ptrdiff_t patch_offset; + uintptr_t module_offset; + }; + + void WriteLoadContext(); + void WriteSaveContext(); + void LockContext(); + void UnlockContext(); + void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id); + void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, + oaknut::SystemReg src_reg); + void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg); + void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg); + +private: + void BranchToPatch(uintptr_t module_dest) { + m_branch_to_patch_relocations.push_back({c.offset(), module_dest}); + } + + void BranchToModule(uintptr_t module_dest) { + m_branch_to_module_relocations.push_back({c.offset(), module_dest}); + c.dw(0); + } + + void WriteModulePc(uintptr_t module_dest) { + m_write_module_pc_relocations.push_back({c.offset(), module_dest}); + c.dx(0); + } + +private: + // List of patch instructions we have generated. + std::vector m_patch_instructions{}; + + // Relocation type for relative branch from module to patch. + struct Relocation { + ptrdiff_t patch_offset; ///< Offset in bytes from the start of the patch section. + uintptr_t module_offset; ///< Offset in bytes from the start of the text section. + }; + + oaknut::VectorCodeGenerator c; + std::vector m_trampolines; + std::vector m_branch_to_patch_relocations{}; + std::vector m_branch_to_module_relocations{}; + std::vector m_write_module_pc_relocations{}; + std::vector m_exclusives{}; + oaknut::Label m_save_context{}; + oaknut::Label m_load_context{}; + PatchMode mode{PatchMode::None}; +}; + +} // namespace Core::NCE diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index a1344f1c41..60ee78e89a 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -16,7 +16,7 @@ #include "core/loader/nso.h" #ifdef HAS_NCE -#include "core/arm/nce/patch.h" +#include "core/arm/nce/patcher.h" #endif namespace Loader { diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 2e73683495..e74697cda9 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -23,7 +23,7 @@ #include "core/memory.h" #ifdef HAS_NCE -#include "core/arm/nce/patch.h" +#include "core/arm/nce/patcher.h" #endif namespace Loader { diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index ade8f85f89..b053a0d147 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -21,7 +21,7 @@ #include "core/memory.h" #ifdef HAS_NCE -#include "core/arm/nce/patch.h" +#include "core/arm/nce/patcher.h" #endif namespace Loader { -- cgit v1.2.3