Parcourir la source

core: Support multiple modules per patcher

GPUCode il y a 2 ans
Parent
commit
d4acdac168

+ 55 - 28
src/core/arm/nce/patcher.cpp

@@ -22,14 +22,10 @@ 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) {
-    // Branch to the first instruction of the module.
-    this->BranchToModule(0);
+Patcher::Patcher() : c(m_patch_instructions) {
+    // The first word of the patch section is always a branch to the first instruction of the
+    // module.
+    c.dw(0);
 
     // Write save context helper function.
     c.l(m_save_context);
@@ -38,6 +34,25 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
     // Write load context helper function.
     c.l(m_load_context);
     WriteLoadContext();
+}
+
+Patcher::~Patcher() = default;
+
+bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
+                        const Kernel::CodeSet::Segment& code) {
+    // 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;
+    }
+
+    // Add a new module patch to our list
+    modules.emplace_back();
+    curr_patch = &modules.back();
+
+    // The first word of the patch section is always a branch to the first instruction of the
+    // module.
+    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);
@@ -94,16 +109,17 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
         }
 
         if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
-            m_exclusives.push_back(i);
+            curr_patch->m_exclusives.push_back(i);
         }
     }
 
     // Determine patching mode for the final relocation step
-    const size_t image_size = program_image.size();
+    total_program_size += image_size;
     this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
+    return true;
 }
 
-void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
+bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
                               const Kernel::CodeSet::Segment& code,
                               Kernel::PhysicalMemory& program_image,
                               EntryTrampolines* out_trampolines) {
@@ -120,7 +136,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         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);
+            rc.B(total_program_size - rel.module_offset + rel.patch_offset);
         }
     };
 
@@ -129,7 +145,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         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);
+            rc.B(rel.module_offset - total_program_size - rel.patch_offset);
         }
     };
 
@@ -137,7 +153,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         if (mode == PatchMode::PreText) {
             return GetInteger(load_base) + patch_offset;
         } else {
-            return GetInteger(load_base) + image_size + patch_offset;
+            return GetInteger(load_base) + total_program_size + patch_offset;
         }
     };
 
@@ -150,39 +166,50 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
     };
 
     // We are now ready to relocate!
-    for (const Relocation& rel : m_branch_to_patch_relocations) {
+    auto& patch = modules[m_relocate_module_index++];
+    for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
         ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
     }
-    for (const Relocation& rel : m_branch_to_module_relocations) {
+    for (const Relocation& rel : patch.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) {
+    for (const Relocation& rel : patch.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) {
+    for (const Trampoline& rel : patch.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) {
+    for (const ModuleTextAddress i : patch.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));
+    // 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;
+
+    // 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(),
+                        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));
+        }
+        return true;
     }
+
+    return false;
 }
 
 size_t Patcher::GetSectionSize() const noexcept {
@@ -322,7 +349,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
 
     // 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});
+    curr_patch->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.

+ 17 - 10
src/core/arm/nce/patcher.h

@@ -31,9 +31,9 @@ public:
     explicit Patcher();
     ~Patcher();
 
-    void PatchText(const Kernel::PhysicalMemory& program_image,
+    bool PatchText(const Kernel::PhysicalMemory& program_image,
                    const Kernel::CodeSet::Segment& code);
-    void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
+    bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
                          Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
     size_t GetSectionSize() const noexcept;
 
@@ -61,16 +61,16 @@ private:
 
 private:
     void BranchToPatch(uintptr_t module_dest) {
-        m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
+        curr_patch->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});
+        curr_patch->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});
+        curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
         c.dx(0);
     }
 
@@ -84,15 +84,22 @@ private:
         uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
     };
 
+    struct ModulePatch {
+        std::vector<Trampoline> m_trampolines;
+        std::vector<Relocation> m_branch_to_patch_relocations{};
+        std::vector<Relocation> m_branch_to_module_relocations{};
+        std::vector<Relocation> m_write_module_pc_relocations{};
+        std::vector<ModuleTextAddress> m_exclusives{};
+    };
+
     oaknut::VectorCodeGenerator c;
-    std::vector<Trampoline> m_trampolines;
-    std::vector<Relocation> m_branch_to_patch_relocations{};
-    std::vector<Relocation> m_branch_to_module_relocations{};
-    std::vector<Relocation> m_write_module_pc_relocations{};
-    std::vector<ModuleTextAddress> m_exclusives{};
     oaknut::Label m_save_context{};
     oaknut::Label m_load_context{};
     PatchMode mode{PatchMode::None};
+    size_t total_program_size{};
+    size_t m_relocate_module_index{};
+    std::vector<ModulePatch> modules;
+    ModulePatch* curr_patch;
 };
 
 } // namespace Core::NCE

+ 2 - 2
src/core/hle/kernel/k_process.cpp

@@ -1233,10 +1233,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
     ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
 
 #ifdef HAS_NCE
-    if (this->IsApplication() && Settings::IsNceEnabled()) {
+    const auto& patch = code_set.PatchSegment();
+    if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
         auto& buffer = m_kernel.System().DeviceMemory().buffer;
         const auto& code = code_set.CodeSegment();
-        const auto& patch = code_set.PatchSegment();
         buffer.Protect(GetInteger(base_addr + code.addr), code.size,
                        Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
         buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,

+ 57 - 18
src/core/loader/deconstructed_rom_directory.cpp

@@ -19,8 +19,54 @@
 #include "core/arm/nce/patcher.h"
 #endif
 
+#ifndef HAS_NCE
+namespace Core::NCE {
+class Patcher {};
+} // namespace Core::NCE
+#endif
+
 namespace Loader {
 
+struct PatchCollection {
+    explicit PatchCollection(bool is_application_) : is_application{is_application_} {
+        module_patcher_indices.fill(-1);
+        patchers.emplace_back();
+    }
+
+    std::vector<Core::NCE::Patcher>* GetPatchers() {
+        if (is_application && Settings::IsNceEnabled()) {
+            return &patchers;
+        }
+        return nullptr;
+    }
+
+    size_t GetTotalPatchSize() const {
+        size_t total_size{};
+#ifdef HAS_NCE
+        for (auto& patcher : patchers) {
+            total_size += patcher.GetSectionSize();
+        }
+#endif
+        return total_size;
+    }
+
+    void SaveIndex(size_t module) {
+        module_patcher_indices[module] = static_cast<s32>(patchers.size() - 1);
+    }
+
+    s32 GetIndex(size_t module) const {
+        return module_patcher_indices[module];
+    }
+
+    s32 GetLastIndex() const {
+        return static_cast<s32>(patchers.size()) - 1;
+    }
+
+    bool is_application;
+    std::vector<Core::NCE::Patcher> patchers;
+    std::array<s32, 13> module_patcher_indices{};
+};
+
 AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
                                                                          bool override_update_)
     : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
@@ -142,18 +188,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
     std::size_t code_size{};
 
     // Define an nce patch context for each potential module.
-#ifdef HAS_NCE
-    std::array<Core::NCE::Patcher, 13> module_patchers;
-#endif
-
-    const auto GetPatcher = [&](size_t i) -> Core::NCE::Patcher* {
-#ifdef HAS_NCE
-        if (is_application && Settings::IsNceEnabled()) {
-            return &module_patchers[i];
-        }
-#endif
-        return nullptr;
-    };
+    PatchCollection patch_ctx{is_application};
 
     // Use the NSO module loader to figure out the code layout
     for (size_t i = 0; i < static_modules.size(); i++) {
@@ -164,13 +199,14 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         }
 
         const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
-        const auto tentative_next_load_addr =
-            AppLoader_NSO::LoadModule(process, system, *module_file, code_size,
-                                      should_pass_arguments, false, {}, GetPatcher(i));
+        const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+            process, system, *module_file, code_size, should_pass_arguments, false, {},
+            patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
         if (!tentative_next_load_addr) {
             return {ResultStatus::ErrorLoadingNSO, {}};
         }
 
+        patch_ctx.SaveIndex(i);
         code_size = *tentative_next_load_addr;
     }
 
@@ -184,6 +220,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         return 0;
     }();
 
+    // Add patch size to the total module size
+    code_size += patch_ctx.GetTotalPatchSize();
+
     // Setup the process code layout
     if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
         return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
@@ -204,9 +243,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
 
         const VAddr load_addr{next_load_addr};
         const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
-        const auto tentative_next_load_addr =
-            AppLoader_NSO::LoadModule(process, system, *module_file, load_addr,
-                                      should_pass_arguments, true, pm, GetPatcher(i));
+        const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+            process, system, *module_file, load_addr, should_pass_arguments, true, pm,
+            patch_ctx.GetPatchers(), patch_ctx.GetIndex(i));
         if (!tentative_next_load_addr) {
             return {ResultStatus::ErrorLoadingNSO, {}};
         }

+ 21 - 20
src/core/loader/nso.cpp

@@ -77,7 +77,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
                                                const FileSys::VfsFile& nso_file, VAddr load_base,
                                                bool should_pass_arguments, bool load_into_process,
                                                std::optional<FileSys::PatchManager> pm,
-                                               Core::NCE::Patcher* patch) {
+                                               std::vector<Core::NCE::Patcher>* patches,
+                                               s32 patch_index) {
     if (nso_file.GetSize() < sizeof(NSOHeader)) {
         return std::nullopt;
     }
@@ -94,8 +95,11 @@ std::optional<VAddr> 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 HAS_NCE
-        if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
-            return patch->GetSectionSize();
+        if (patches && load_into_process) {
+            auto* patch = &patches->operator[](patch_index);
+            if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
+                return patch->GetSectionSize();
+            }
         }
 #endif
         return 0;
@@ -160,27 +164,24 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
 #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) {
+    auto* patch = patches ? &patches->operator[](patch_index) : nullptr;
+    if (patch && !load_into_process) {
         // Patch SVCs and MRS calls in the guest code
-        patch->PatchText(program_image, code);
-
-        // Add patch section size to the module size.
-        image_size += static_cast<u32>(patch->GetSectionSize());
+        while (!patch->PatchText(program_image, code)) {
+            patch = &patches->emplace_back();
+        }
     } 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->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
-        patch_segment.size = static_cast<u32>(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->GetPatchMode() == Core::NCE::PatchMode::PostData) {
-            image_size += patch_segment.size;
+        if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
+            // Update patch section.
+            auto& patch_segment = codeset.PatchSegment();
+            patch_segment.addr =
+                patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
+            patch_segment.size = static_cast<u32>(patch->GetSectionSize());
         }
+
+        // Refresh image_size to take account the patch section if it was added by RelocateAndCopy
+        image_size = static_cast<u32>(program_image.size());
     }
 #endif
 

+ 2 - 1
src/core/loader/nso.h

@@ -93,7 +93,8 @@ public:
                                            const FileSys::VfsFile& nso_file, VAddr load_base,
                                            bool should_pass_arguments, bool load_into_process,
                                            std::optional<FileSys::PatchManager> pm = {},
-                                           Core::NCE::Patcher* patch = nullptr);
+                                           std::vector<Core::NCE::Patcher>* patches = nullptr,
+                                           s32 patch_index = -1);
 
     LoadResult Load(Kernel::KProcess& process, Core::System& system) override;