Merge pull request #12677 from GPUCode/whyy-modders
core: Support multiple modules per patcher
This commit is contained in:
		| @@ -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. | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -1239,10 +1239,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, | ||||
|   | ||||
| @@ -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, {}}; | ||||
|         } | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user