From b7f00664ade367a75c8c17fefed6955858e480fa Mon Sep 17 00:00:00 2001 From: Michael Zhao <44533763+Pistonight@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:59:20 -0800 Subject: [PATCH] refactor exl patching (#4) --- LICENSE | 2 +- README.md | 3 + lib/build/configure.rs | 2 +- lib/include/exl/hook/inline.hpp | 4 +- lib/include/exl/hook/lib.hpp | 8 - lib/include/exl/hook/replace.hpp | 4 +- lib/include/exl/hook/trampoline.hpp | 3 +- lib/include/exl/hook/util/jit.hpp | 36 ---- lib/include/exl/hook/util/modules.hpp | 15 -- lib/include/exl/hook/util/setting.hpp | 26 --- lib/include/exl/patch/code_patcher.hpp | 42 ---- lib/include/exl/patch/patcher_impl.hpp | 60 ------ .../exl/patch/random_access_patcher.hpp | 67 ------- lib/include/exl/patch/stream_patcher.hpp | 78 -------- lib/include/exl/util/sys/mem_layout.hpp | 69 ------- lib/include/exl/util/sys/rw_pages.hpp | 107 ---------- lib/include/exl/util/typed_storage.hpp | 69 ------- lib/include/exl_patch/prelude.h | 7 - lib/include/megaton/__priv/aligned_storage.h | 58 ++++++ lib/include/megaton/__priv/jit.h | 43 +++++ lib/include/megaton/__priv/mirror.h | 77 ++++++++ lib/include/megaton/__priv/proc_handle.h | 8 + lib/include/megaton/module_layout.h | 87 +++++++++ lib/include/megaton/patch.h | 167 ++++++++++++++++ lib/include/megaton/prelude.h | 2 + lib/src/exl/{LICENSE.md => LICENSE} | 0 lib/src/exl/hook/nx64/hook_impl.cpp | 44 +++-- lib/src/exl/hook/nx64/inline_impl.cpp | 27 ++- lib/src/exl/util/sys/cur_proc_handle.hpp | 7 - lib/src/exl/util/sys/mem_layout.cpp | 182 ------------------ lib/src/exl/util/sys/rw_pages.cpp | 127 ------------ lib/src/megaton/__priv/mirror.cpp | 122 ++++++++++++ .../__priv/proc_handle.cpp} | 50 ++--- lib/src/megaton/init.cpp | 12 +- lib/src/megaton/module_layout.cpp | 123 ++++++++++++ lib/src/megaton/patcher.cpp | 41 ++++ 36 files changed, 818 insertions(+), 961 deletions(-) delete mode 100644 lib/include/exl/hook/util/jit.hpp delete mode 100644 lib/include/exl/hook/util/modules.hpp delete mode 100644 lib/include/exl/hook/util/setting.hpp delete mode 100644 lib/include/exl/patch/code_patcher.hpp delete mode 100644 lib/include/exl/patch/patcher_impl.hpp delete mode 100644 lib/include/exl/patch/random_access_patcher.hpp delete mode 100644 lib/include/exl/patch/stream_patcher.hpp delete mode 100644 lib/include/exl/util/sys/mem_layout.hpp delete mode 100644 lib/include/exl/util/sys/rw_pages.hpp delete mode 100644 lib/include/exl/util/typed_storage.hpp delete mode 100644 lib/include/exl_patch/prelude.h create mode 100644 lib/include/megaton/__priv/aligned_storage.h create mode 100644 lib/include/megaton/__priv/jit.h create mode 100644 lib/include/megaton/__priv/mirror.h create mode 100644 lib/include/megaton/__priv/proc_handle.h create mode 100644 lib/include/megaton/module_layout.h create mode 100644 lib/include/megaton/patch.h rename lib/src/exl/{LICENSE.md => LICENSE} (100%) delete mode 100644 lib/src/exl/util/sys/cur_proc_handle.hpp delete mode 100644 lib/src/exl/util/sys/mem_layout.cpp delete mode 100644 lib/src/exl/util/sys/rw_pages.cpp create mode 100644 lib/src/megaton/__priv/mirror.cpp rename lib/src/{exl/util/sys/cur_proc_handle.cpp => megaton/__priv/proc_handle.cpp} (67%) create mode 100644 lib/src/megaton/module_layout.cpp create mode 100644 lib/src/megaton/patcher.cpp diff --git a/LICENSE b/LICENSE index da9f079..3774147 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Michael +Copyright (c) 2024 Michael Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 426dd0d..ffa830b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Megaton is a build tool and support library for embedding Rust in a NSO binary. [(name reference)](https://www.zeldadungeon.net/wiki/Rusty_Switch) +## LICENSE +libmegaton is GPLv2. Everything else is MIT. + ## Install TODO diff --git a/lib/build/configure.rs b/lib/build/configure.rs index cba44ac..b8314b3 100644 --- a/lib/build/configure.rs +++ b/lib/build/configure.rs @@ -98,7 +98,7 @@ fn build_ninja(cli: &Cli) -> Result<(), Error> { let includes = [&inc_root, &env.libnx_include]; // let mut c_flags = flags::DEFAULT_C.to_vec(); - let mut c_flags = vec!["-Wall", "-Werror", "-O3"]; + let mut c_flags = vec!["-Wall", "-Werror", "-O3", "-fdiagnostics-color=always"]; c_flags.push("-DMEGATON_LIB"); let include_flag = includes diff --git a/lib/include/exl/hook/inline.hpp b/lib/include/exl/hook/inline.hpp index cfee1f7..af09558 100644 --- a/lib/include/exl/hook/inline.hpp +++ b/lib/include/exl/hook/inline.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include @@ -17,7 +17,7 @@ template struct InlineHook { inline_always_ void InstallAtOffset(ptrdiff_t address) { _HOOK_STATIC_CALLBACK_ASSERT(); - hook::HookInline(util::modules::GetTargetStart() + address, + hook::HookInline(megaton::module::main_info().start() + address, Derived::Callback); } diff --git a/lib/include/exl/hook/lib.hpp b/lib/include/exl/hook/lib.hpp index b7593c6..2298b95 100644 --- a/lib/include/exl/hook/lib.hpp +++ b/lib/include/exl/hook/lib.hpp @@ -6,7 +6,6 @@ #include #include #include -#include namespace exl::hook { @@ -17,13 +16,6 @@ namespace { using Entrypoint = util::GenericFuncPtr; }; -/* inline noreturn_ CallTargetEntrypoint(void* x0, void* x1) { */ -/* auto entrypoint = - * reinterpret_cast(util::modules::GetTargetStart()); */ -/* entrypoint(x0, x1); */ -/* __builtin_unreachable(); */ -/* } */ - inline void Initialize() { arch::Initialize(); arch::InitializeInline(); diff --git a/lib/include/exl/hook/replace.hpp b/lib/include/exl/hook/replace.hpp index 3def96c..f8e3a8d 100644 --- a/lib/include/exl/hook/replace.hpp +++ b/lib/include/exl/hook/replace.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include @@ -18,7 +18,7 @@ template struct ReplaceHook { inline_always_ void InstallAtOffset(ptrdiff_t address) { _HOOK_STATIC_CALLBACK_ASSERT(); - hook::Hook(util::modules::GetTargetStart() + address, + hook::Hook(megaton::module::main_info().start() + address, Derived::Callback); } diff --git a/lib/include/exl/hook/trampoline.hpp b/lib/include/exl/hook/trampoline.hpp index a209648..390fc33 100644 --- a/lib/include/exl/hook/trampoline.hpp +++ b/lib/include/exl/hook/trampoline.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -34,7 +35,7 @@ template class TrampolineHook { inline_always_ void InstallAtOffset(ptrdiff_t address) { _HOOK_STATIC_CALLBACK_ASSERT(); - OrigRef() = hook::Hook(util::modules::GetTargetStart() + address, + OrigRef() = hook::Hook(megaton::module::main_info().start() + address, Derived::Callback, true); } diff --git a/lib/include/exl/hook/util/jit.hpp b/lib/include/exl/hook/util/jit.hpp deleted file mode 100644 index ee4934b..0000000 --- a/lib/include/exl/hook/util/jit.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include - -#include -#include - -#define JIT_CREATE(name, size) \ - namespace impl::name { \ - __attribute__((section(".text." #name))) alignas( \ - PAGE_SIZE) static const std::array s_Area{}; \ - } \ - exl::util::Jit name(std::span(impl::name::s_Area)); - -namespace exl::util { - -class Jit { - std::span m_Rx; - util::TypedStorage m_Pages; - - inline RwPages& GetPages() { return util::GetReference(m_Pages); } - -public: - constexpr Jit(std::span rx) : m_Rx(rx) {} - inline void Initialize() { - util::ConstructAt(m_Pages, reinterpret_cast(m_Rx.data()), - m_Rx.size()); - } - - inline void Flush() { GetPages().Flush(); } - - inline uintptr_t GetRo() { return GetPages().GetRo(); } - inline uintptr_t GetRw() { return GetPages().GetRw(); } - inline uintptr_t GetSize() { return GetPages().GetSize(); } -}; -} // namespace exl::util diff --git a/lib/include/exl/hook/util/modules.hpp b/lib/include/exl/hook/util/modules.hpp deleted file mode 100644 index e5e2b77..0000000 --- a/lib/include/exl/hook/util/modules.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -namespace exl::util::modules { - -inline uintptr_t GetSelfStart() { return GetSelfModuleInfo().m_Total.m_Start; } - -inline uintptr_t GetTargetOffset(uintptr_t offset) { - return GetMainModuleInfo().m_Total.m_Start + offset; -} - -inline uintptr_t GetTargetStart() { return GetTargetOffset(0); } - -} // namespace exl::util::modules diff --git a/lib/include/exl/hook/util/setting.hpp b/lib/include/exl/hook/util/setting.hpp deleted file mode 100644 index e117216..0000000 --- a/lib/include/exl/hook/util/setting.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include - -#ifndef EXL_JIT_SIZE -#define EXL_JIT_SIZE 0x1000 -#endif - -#ifndef EXL_INLINE_POOL_SIZE -#define EXL_INLINE_POOL_SIZE 0x1000 -#endif - -namespace exl::setting { - -/* How large the JIT area will be for hooks. */ -constexpr usize JitSize = EXL_JIT_SIZE; - -/* How large the area will be inline hook pool. */ -constexpr usize InlinePoolSize = EXL_INLINE_POOL_SIZE; - -/* Sanity checks. */ -static_assert(align_up_(JitSize, PAGE_SIZE) == JitSize, - "JitSize is not aligned"); -static_assert(align_up_(InlinePoolSize, PAGE_SIZE) == JitSize, - "InlinePoolSize is not aligned"); -} // namespace exl::setting diff --git a/lib/include/exl/patch/code_patcher.hpp b/lib/include/exl/patch/code_patcher.hpp deleted file mode 100644 index 341fc9d..0000000 --- a/lib/include/exl/patch/code_patcher.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include -#include - -namespace exl::patch { - -namespace inst = armv8::inst; - -class CodePatcher : public StreamPatcher { -private: - using InstBitSet = armv8::InstBitSet; - -public: - inline CodePatcher(uintptr_t start) : StreamPatcher(start) {} - - inline void WriteInst(InstBitSet inst) { Write(inst); } - - /* Special case branches as they are relative to the current position. */ - inline void BranchInstRel(ptrdiff_t address) { - WriteInst(inst::Branch(address)); - } - inline void BranchLinkInstRel(ptrdiff_t address) { - WriteInst(inst::BranchLink(address)); - } - - /* Address relative to the base (Ro). */ - inline void BranchInst(uintptr_t address) { - BranchInstRel(RelativeAddressFromBase(address)); - } - inline void BranchLinkInst(uintptr_t address) { - BranchLinkInstRel(RelativeAddressFromBase(address)); - } - /* Absolute addresses. */ - inline void BranchInst(void* ptr) { - BranchInstRel(RelativeAddressFromPointer(ptr)); - } - inline void BranchLinkInst(void* ptr) { - BranchLinkInstRel(RelativeAddressFromPointer(ptr)); - } -}; -} // namespace exl::patch diff --git a/lib/include/exl/patch/patcher_impl.hpp b/lib/include/exl/patch/patcher_impl.hpp deleted file mode 100644 index 738ddc9..0000000 --- a/lib/include/exl/patch/patcher_impl.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace exl::patch { - -namespace impl { -inline util::TypedStorage s_Storage; - -inline const util::RwPages& GetRwPages() { - return util::GetReference(s_Storage); -} - -inline void InitPatcherImpl() { - auto& mod = util::GetMainModuleInfo(); - auto start = mod.m_Total.m_Start; - auto size = mod.m_Rodata.GetEnd() - start; - util::ConstructAt(s_Storage, start, size); -} -}; // namespace impl - -class PatcherImpl { -protected: - const util::RwPages& m_Pages; - - template inline T& At(const uintptr_t offset) { - /* Get address for the object. */ - uintptr_t start = RwFromAddr(offset); - - auto ptr = reinterpret_cast(start); - return *ptr; - } - -public: - inline PatcherImpl() : m_Pages(impl::GetRwPages()) {} - - inline uintptr_t AddrFromRo(uintptr_t ro) const { - return ro - m_Pages.GetRo(); - } - inline uintptr_t AddrFromRw(uintptr_t rw) const { - return rw - m_Pages.GetRw(); - } - - inline uintptr_t RoFromAddr(uintptr_t addr) const { - return m_Pages.GetRo() + addr; - } - inline uintptr_t RwFromAddr(uintptr_t addr) const { - return m_Pages.GetRw() + addr; - } - - inline ptrdiff_t AddrFromRoPointer(void* ptr) const { - return AddrFromRo(reinterpret_cast(ptr)); - } - inline ptrdiff_t AddrFromRwPointer(void* ptr) const { - return AddrFromRw(reinterpret_cast(ptr)); - } -}; -} // namespace exl::patch diff --git a/lib/include/exl/patch/random_access_patcher.hpp b/lib/include/exl/patch/random_access_patcher.hpp deleted file mode 100644 index 4a5773a..0000000 --- a/lib/include/exl/patch/random_access_patcher.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -extern "C" { -#include -} - -namespace exl::patch { - -class RandomAccessPatcher : public PatcherImpl { -private: - uintptr_t m_Lowest = UINTPTR_MAX; - uintptr_t m_Highest = UINTPTR_MAX; - - inline bool IsUnset() { - return m_Lowest == UINTPTR_MAX || m_Highest == UINTPTR_MAX; - } - - inline void SetLowest(uintptr_t addr) { - if (IsUnset()) - m_Lowest = addr; - else - m_Lowest = std::min(m_Lowest, addr); - } - - inline void SetHighest(uintptr_t addr) { - if (IsUnset()) - m_Highest = addr; - else - m_Highest = std::max(m_Highest, addr); - } - -public: - template void Write(const uintptr_t addr, T value) { - /* Update low/high. */ - SetLowest(addr); - SetHighest(addr + sizeof(T)); - - /* Write value. */ - At(addr) = value; - } - - template T& Read(const uintptr_t index) { - return At(index); - } - - inline void Flush() { - /* Nothing to flush if there was nothing done. */ - if (IsUnset()) - return; - - /* Get actual pointers. */ - void* ro = (void*)RoFromAddr(m_Lowest); - void* rw = (void*)RwFromAddr(m_Lowest); - auto size = m_Highest - m_Lowest; - - /* Flush data/instructions. */ - armDCacheFlush(rw, size); - armICacheInvalidate(ro, size); - } - - inline ~RandomAccessPatcher() { - /* Flush out any changes we made. */ - Flush(); - } -}; -}; // namespace exl::patch diff --git a/lib/include/exl/patch/stream_patcher.hpp b/lib/include/exl/patch/stream_patcher.hpp deleted file mode 100644 index 775a101..0000000 --- a/lib/include/exl/patch/stream_patcher.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -extern "C" { -#include -} - -namespace exl::patch { - -class StreamPatcher : public PatcherImpl { -private: - constexpr bool DoneNothing() const { return m_Start == m_Current; } - - inline void Flush() { - /* Nothing to flush if there was nothing done. */ - if (DoneNothing()) - return; - - /* Get actual pointers. */ - void* ro = (void*)RoFromAddr(m_Start); - void* rw = (void*)RwFromAddr(m_Start); - auto size = m_Current - m_Start; - - /* Flush data/instructions. */ - armDCacheFlush(rw, size); - armICacheInvalidate(ro, size); - - /* Reset start position. */ - m_Start += size; - } - -protected: - inline ptrdiff_t RelativeAddressFromBase(uintptr_t address) const { - return address - m_Current; - } - inline ptrdiff_t RelativeAddressFromPointer(void* ptr) const { - return AddrFromRoPointer(ptr) - m_Current; - } - - uintptr_t m_Start; - uintptr_t m_Current; - -public: - inline StreamPatcher(uintptr_t start) : m_Start(start), m_Current(start) {} - - template inline void Write(T v) { - At(m_Current) = v; - - m_Current += sizeof(T); - } - - /* Flush current data then move to a new address. */ - inline void SeekRel(uintptr_t address) { - /* Don't need to do anything if the address doesn't need to change. */ - if (address == 0) - return; - - /* Get address relative to base. */ - address += m_Current; - - /* Flush what we've already done. */ - Flush(); - - /* Reset to the position to the provided address. */ - m_Start = address; - m_Current = address; - } - - /* Address relative to the base (Ro). */ - inline void Seek(uintptr_t address) { - SeekRel(RelativeAddressFromBase(address)); - } - /* Absolute address. */ - inline void Seek(void* ptr) { SeekRel(RelativeAddressFromPointer(ptr)); } - - inline ~StreamPatcher() { Flush(); } -}; -} // namespace exl::patch diff --git a/lib/include/exl/util/sys/mem_layout.hpp b/lib/include/exl/util/sys/mem_layout.hpp deleted file mode 100644 index d89f65c..0000000 --- a/lib/include/exl/util/sys/mem_layout.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include - -#include - -namespace exl::util { - -struct Range { - uintptr_t m_Start; - size_t m_Size; - - constexpr uintptr_t GetEnd() const { return m_Start + m_Size; } -}; - -struct ModuleInfo { - Range m_Total; - Range m_Text; - Range m_Rodata; - Range m_Data; - /* TODO: bss? */ -}; - -namespace mem_layout { -static constexpr int s_MaxModules = 13; -inline int s_ModuleCount = -1; - -inline Range s_Alias; -inline Range s_Heap; -inline Range s_Aslr; -inline Range s_Stack; -} // namespace mem_layout - -namespace impl { -void InitMemLayout(); - -namespace mem_layout { -inline std::array s_ModuleInfos; -} -} // namespace impl - -static inline const ModuleInfo& GetModuleInfo(int index) { - assert_(index < mem_layout::s_ModuleCount); - return impl::mem_layout::s_ModuleInfos.at(index); -} - -namespace mem_layout { - -static constexpr int s_RtldModuleIdx = 0; -static constexpr int s_MainModuleIdx = 1; - -/* Decided at runtime. */ -inline int s_SelfModuleIdx = -1; - -} // namespace mem_layout -static inline const ModuleInfo& GetRtldModuleInfo() { - return GetModuleInfo(mem_layout::s_RtldModuleIdx); -} -static inline const ModuleInfo& GetMainModuleInfo() { - return GetModuleInfo(mem_layout::s_MainModuleIdx); -} - -static inline const ModuleInfo& GetSelfModuleInfo() { - return GetModuleInfo(mem_layout::s_SelfModuleIdx); -} -static inline const ModuleInfo& GetSdkModuleInfo() { - return GetModuleInfo(mem_layout::s_ModuleCount - 1); -} -}; // namespace exl::util diff --git a/lib/include/exl/util/sys/rw_pages.hpp b/lib/include/exl/util/sys/rw_pages.hpp deleted file mode 100644 index 2c83bbf..0000000 --- a/lib/include/exl/util/sys/rw_pages.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once -#include - -#include - -#include - -typedef struct VirtmemReservation VirtmemReservation; - -namespace exl::util { - -class RwPages { - // not copyable - RwPages(const RwPages&) = delete; - RwPages& operator=(const RwPages&) = delete; - -private: - struct Claim { - uintptr_t m_Ro = 0; - uintptr_t m_Rw = 0; - size_t m_Size = 0; - VirtmemReservation* m_RwReserve = nullptr; - - constexpr uintptr_t GetAlignedRo() const { - return align_down_(m_Ro, PAGE_SIZE); - } - - constexpr uintptr_t GetAlignedRw() const { - return align_down_(m_Rw, PAGE_SIZE); - } - - constexpr size_t GetAlignedSize() const { - return align_up_(m_Size, PAGE_SIZE); - } - - constexpr ptrdiff_t RoToOffset(uintptr_t address) const { - return m_Ro - address; - } - - constexpr ptrdiff_t RwToOffset(uintptr_t address) const { - return m_Rw - address; - } - - constexpr uintptr_t RoToRw(uintptr_t address) const { - return m_Rw + RoToOffset(address); - } - - constexpr uintptr_t RwToRo(uintptr_t address) const { - return RwToOffset(address) + m_Ro; - } - - constexpr bool InRo(uintptr_t address) const { - ptrdiff_t offset = RoToOffset(address); - - /* Is the address before the ro region? */ - if (offset < 0) - return false; - - /* Is the address after the ro region? */ - if (m_Size <= static_cast(offset)) - return false; - - return true; - } - - constexpr bool InRw(uintptr_t address) const { - ptrdiff_t offset = RwToOffset(address); - - /* Is the address before the rw region? */ - if (offset < 0) - return false; - - /* Is the address after the rw region? */ - if (m_Size <= static_cast(offset)) - return false; - - return true; - } - }; - - Claim m_Claim; - bool m_Owner = true; - -public: - RwPages(uintptr_t ro, size_t size); - - /* Explicitly only allow moving. */ - RwPages(RwPages&& other) - : m_Claim(std::exchange(other.m_Claim, {})), - m_Owner(std::exchange(other.m_Owner, false)) {} - RwPages& operator=(RwPages&& other) { - m_Claim = std::exchange(other.m_Claim, {}); - other.m_Owner = false; - return *this; - } - - void Flush(); - - inline const Claim& GetClaim() const { return m_Claim; } - - inline uintptr_t GetRo() const { return GetClaim().m_Ro; } - inline uintptr_t GetRw() const { return GetClaim().m_Rw; } - inline uintptr_t GetSize() const { return GetClaim().m_Size; } - - ~RwPages(); -}; -}; // namespace exl::util diff --git a/lib/include/exl/util/typed_storage.hpp b/lib/include/exl/util/typed_storage.hpp deleted file mode 100644 index 39b539d..0000000 --- a/lib/include/exl/util/typed_storage.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include -#include - -#include - -namespace exl::util { - -template -struct TypedStorage { - typename std::aligned_storage::type _storage; -}; - -template inline_always_ T* GetPointer(TypedStorage& ts) { - return std::launder(reinterpret_cast(std::addressof(ts._storage))); -} - -template -inline_always_ const T* GetPointer(const TypedStorage& ts) { - return std::launder( - reinterpret_cast(std::addressof(ts._storage))); -} - -template inline_always_ T& GetReference(TypedStorage& ts) { - return *GetPointer(ts); -} - -template -inline_always_ const T& GetReference(const TypedStorage& ts) { - return *GetPointer(ts); -} - -namespace impl { - -template -inline_always_ T* GetPointerForConstructAt(TypedStorage& ts) { - return reinterpret_cast(std::addressof(ts._storage)); -} - -} // namespace impl - -template -inline_always_ T* ConstructAt(TypedStorage& ts, Args&&... args) { - return std::construct_at(impl::GetPointerForConstructAt(ts), - std::forward(args)...); -} - -template inline_always_ void DestroyAt(TypedStorage& ts) { - return std::destroy_at(GetPointer(ts)); -} - -} // namespace exl::util diff --git a/lib/include/exl_patch/prelude.h b/lib/include/exl_patch/prelude.h deleted file mode 100644 index b4cb8e7..0000000 --- a/lib/include/exl_patch/prelude.h +++ /dev/null @@ -1,7 +0,0 @@ -/** Prelude includes for working with patching */ -#pragma once - -#include -#include -#include -#include diff --git a/lib/include/megaton/__priv/aligned_storage.h b/lib/include/megaton/__priv/aligned_storage.h new file mode 100644 index 0000000..62d59b6 --- /dev/null +++ b/lib/include/megaton/__priv/aligned_storage.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Adapted from exlaunch for megaton project + */ + +#pragma once +#include +#include +#include + +#include + +namespace megaton::__priv { + +/** Aligned storage for an object of type T. */ +template +class AlignedStorage { + +public: + inline_member_ T* pointer() { return std::launder(pointer_internal()); } + inline_member_ const T* pointer() const { + return std::launder(pointer_internal()); + } + inline_member_ T& reference() { return *pointer(); } + inline_member_ const T& reference() const { return *pointer(); } + template inline_member_ T* construct(Args&&... args) { + return std::construct_at(pointer_internal(), + std::forward(args)...); + } + + inline_member_ void destroy() { + return std::destroy_at(pointer_internal()); + } + +private: + typename std::aligned_storage::type _storage; + + inline_member_ T* pointer_internal() { + return reinterpret_cast(std::addressof(_storage)); + } +}; + +} // namespace megaton::__priv diff --git a/lib/include/megaton/__priv/jit.h b/lib/include/megaton/__priv/jit.h new file mode 100644 index 0000000..94b92ad --- /dev/null +++ b/lib/include/megaton/__priv/jit.h @@ -0,0 +1,43 @@ +/* + * This file is part of exlaunch, adapted for megaton project + * + * exlaunch is licensed under GPLv2 + */ +#pragma once + +#include +#include + +/** Make JIT memory with the given identifier and size. */ +#define make_jit_(name, size) \ + namespace __jit::name { \ + alignas(PAGE_SIZE) __attribute__(( \ + section(".text.jit_" #name))) static const u8 code[size]{}; \ + } \ + megaton::__priv::Jit name(__jit::name::code, size); + +namespace megaton::__priv { + +class Jit { + +public: + Jit(const u8* start, size_t size) : start((uintptr_t)start), _size(size) {} + + /** Initialize the JIT memory region */ + inline_member_ void init() { mirror.construct(start, _size); } + + inline_member_ void flush() { get_mirror().flush(); } + inline_member_ uintptr_t ro_start() { return get_mirror().ro_start(); } + inline_member_ uintptr_t rw_start() { return get_mirror().rw_start(); } + inline_member_ uintptr_t size() { return get_mirror().size(); } + +private: + /** Read-execute memory. */ + uintptr_t start; + size_t _size; + /** Mapped writable memory. */ + AlignedStorage mirror; + + inline_member_ Mirror& get_mirror() { return mirror.reference(); } +}; +} // namespace megaton::__priv diff --git a/lib/include/megaton/__priv/mirror.h b/lib/include/megaton/__priv/mirror.h new file mode 100644 index 0000000..7bb1c1d --- /dev/null +++ b/lib/include/megaton/__priv/mirror.h @@ -0,0 +1,77 @@ +/* + * This file is part of exlaunch, adapted for megaton project + * + * exlaunch is licensed under GPLv2 + */ +#pragma once +#include +#include + +#include + +namespace megaton::__priv { +/** + * Writable memory mapped to read-only memory, + * to facilitate patching + */ +class Mirror { +private: + class Info { + friend class Mirror; + /** Start of the read-only region. */ + uintptr_t ro_start = 0; + /** Start of the read-write region. */ + uintptr_t rw_start = 0; + /** Size of the region. */ + size_t size = 0; + /** Reservation for the read-write region. */ + struct VirtmemReservation* rw_reserve = nullptr; + + /** Page-aligned start of the read-only region. */ + constexpr uintptr_t ro_start_aligned() const { + return align_down_(ro_start, PAGE_SIZE); + } + + /** Page-aligned start of the read-write region. */ + constexpr uintptr_t rw_start_aligned() const { + return align_down_(rw_start, PAGE_SIZE); + } + + /** Page-aligned size of the region. */ + constexpr size_t size_aligned() const { + return align_up_(size, PAGE_SIZE); + } + }; + +public: + /** Map writable memory to the (read-only) region with start and size */ + Mirror(uintptr_t start, size_t size); + ~Mirror(); + + /* Not copyable. */ + Mirror(const Mirror&) = delete; + Mirror& operator=(const Mirror&) = delete; + + /* Explicitly only allow moving. */ + Mirror(Mirror&& other) : m(std::exchange(other.m, {})) {} + Mirror& operator=(Mirror&& other) { + m = std::exchange(other.m, {}); + return *this; + } + + /** Flush written changes to physical memory. */ + void flush(); + + /** Get the start of the read-only region. */ + inline uintptr_t ro_start() const { return m.ro_start; } + /** Get the start of the read-write region. */ + inline uintptr_t rw_start() const { return m.rw_start; } + /** Get the size of the region. */ + inline uintptr_t size() const { return m.size; } + +private: + inline const Info& info() const { return m; } + + Info m; +}; +}; // namespace megaton::__priv diff --git a/lib/include/megaton/__priv/proc_handle.h b/lib/include/megaton/__priv/proc_handle.h new file mode 100644 index 0000000..9e45938 --- /dev/null +++ b/lib/include/megaton/__priv/proc_handle.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace megaton::__priv { +/** Get the handle for the current process. */ +Handle current_process(); +} // namespace megaton::__priv diff --git a/lib/include/megaton/module_layout.h b/lib/include/megaton/module_layout.h new file mode 100644 index 0000000..f75435c --- /dev/null +++ b/lib/include/megaton/module_layout.h @@ -0,0 +1,87 @@ +/** + * Information about the physical memory layout of the modules at runtime + * + * This file is partially adapted from exlaunch, which is licensed under GPLv2 + */ +#pragma once +#include + +namespace megaton::module { + +/** + * Initialize the module layout + * + * This searches the entire address space to find the loaded modules. + * This is automatically called in the megaton entrypoint, before + * user-defined megaton_main + */ +void init_layout(); + +/** Get the number of modules */ +u32 count(); + +/** + * Info about a module + * + * Each module layout should be text -> rodata -> data + */ +class Info { +public: + class Range { + friend class Info; + + public: + inline_member_ constexpr uintptr_t end() const { + return _start + _size; + } + inline_member_ constexpr uintptr_t start() const { return _start; } + inline_member_ constexpr size_t size() const { return _size; } + inline_member_ void set(uintptr_t start, size_t size) { + _start = start; + _size = size; + } + + private: + uintptr_t _start; + size_t _size; + }; + + inline_member_ constexpr uintptr_t start() const { return _text.start(); } + inline_member_ constexpr uintptr_t end() const { return _data.end(); } + inline_member_ constexpr size_t size() const { + return _text.size() + _rodata.size() + _data.size(); + } + inline_member_ constexpr const Range& text() const { return _text; } + inline_member_ constexpr const Range& rodata() const { return _rodata; } + inline_member_ constexpr const Range& data() const { return _data; } + + inline_member_ constexpr Range& text() { return _text; } + inline_member_ constexpr Range& rodata() { return _rodata; } + inline_member_ constexpr Range& data() { return _data; } + +private: + Range _text; + Range _rodata; + Range _data; + /* TODO: bss? */ +}; + +/** Get the module info at the given index */ +const Info& info_at(u32 index); + +static constexpr u32 RTLD_MODULE_IDX = 0; +static constexpr u32 MAIN_MODULE_IDX = 1; + +/** Get the module info for the main module */ +inline_always_ const Info& main_info() { return info_at(MAIN_MODULE_IDX); } + +/** Get the module info for the rtld module */ +inline_always_ const Info& rtld_info() { return info_at(RTLD_MODULE_IDX); } + +/** Get the SDK module info */ +const Info& sdk_info(); + +/** Get the info for this module */ +const Info& self_info(); + +} // namespace megaton::module diff --git a/lib/include/megaton/patch.h b/lib/include/megaton/patch.h new file mode 100644 index 0000000..97c3d6d --- /dev/null +++ b/lib/include/megaton/patch.h @@ -0,0 +1,167 @@ +/** + * Runtime patching system + */ +#pragma once +#include +#include + +namespace megaton::patch { + +/** + * Initialize the patching system + * + * This is automatically called in the megaton entrypoint, before + * user-defined megaton_main + */ +void init(); + +/** + * Access the main read-only regions (text and rodata) + */ +const __priv::Mirror& main_ro(); + +/** + * A Branch instruction payload + * + * Used with Stream to write a branch instruction. + * The relative address will be resolved at write-time. + */ +class Branch { +public: + explicit Branch(uintptr_t target, bool link) : target(target), link(link) {} + + exl::armv8::InstBitSet get_insn(uintptr_t ro_current_addr) { + // relative address to jump to the function + ptrdiff_t rel_addr = target - ro_current_addr; + if (link) { + return exl::armv8::inst::BranchLink(rel_addr); + } else { + return exl::armv8::inst::Branch(rel_addr); + } + } + +private: + uintptr_t target; + bool link; +}; + +/** Create a branch to a function to write to a patching stream */ +template inline_always_ Branch b(F* func) { + return Branch{reinterpret_cast(func), false}; +} + +/** Create a branch link to a function to write to a patching stream */ +template inline_always_ Branch bl(F* func) { + return Branch{reinterpret_cast(func), true}; +} + +/** + * A repeat payload + * + * Writing this payload is equivalent to writing the same instruction multiple + * times + */ +template class Repeat { +public: + Repeat(T insn, size_t count) : insn(insn), count(count) {} + T insn; + size_t count; +}; + +template inline_always_ Repeat repeat(T insn, size_t count) { + return {insn, count}; +} + +/** + * A skip payload + * + * Writing thie payload is equivalent to calling skip() on the stream + */ +class Skip { +public: + explicit Skip(size_t count) : count(count) {} + size_t count; +}; +inline_always_ Skip skip(size_t count) { return Skip{count}; } + +/** + * A patcher stream. + * + * The stream starts at an offset of the main module, and + * the `<<` operator can be used to patch the instructions. + * The stream will advance by the size of the instruction and + * will automatically flush when destroyed. + */ +class Stream { +public: + Stream(const __priv::Mirror& mirror, uintptr_t start_offset) + : mirror(mirror) { + rw_start_addr = mirror.rw_start() + start_offset; + ro_start_addr = mirror.ro_start() + start_offset; + rw_current_addr = rw_start_addr; + } + + ~Stream() { flush(); } + + /** Flush the instructions written so far */ + void flush(); + + Stream& operator<<(exl::armv8::InstBitSet inst) { + write(inst); + return *this; + } + + Stream& operator<<(Branch branch) { + // find the actual physical address of the write head + uintptr_t ro_current_addr = + rw_current_addr - mirror.rw_start() + mirror.ro_start(); + write(branch.get_insn(ro_current_addr)); + return *this; + } + + template Stream& operator<<(Repeat repeat) { + for (size_t i = 0; i < repeat.count; i++) { + write(repeat.insn); + } + return *this; + } + + Stream& operator<<(Skip skip) { + this->skip(skip.count); + return *this; + } + +private: + /** The underlying memory */ + const __priv::Mirror& mirror; + /** The starting address of the stream in the read-only region */ + uintptr_t ro_start_addr; + /** The starting address of the stream in the read-write region */ + uintptr_t rw_start_addr; + /** The current address of the stream (where to write next) in the RW region + */ + uintptr_t rw_current_addr; + + exl::armv8::InstBitSet& at(const uintptr_t rw_offset) { + auto ptr = reinterpret_cast(rw_offset); + return *ptr; + } + + /** Write the value at the current position of the stream, and advance */ + void write(exl::armv8::InstBitSet value) { + at(rw_current_addr) = value; + rw_current_addr += sizeof(exl::armv8::InstBitSet); + } + + /** Skip `count` instructions. i.e. advance the stream without writing */ + void skip(size_t count) { + rw_current_addr += sizeof(exl::armv8::InstBitSet) * count; + } +}; + +/** Create a stream to patch the main module at the starting offset */ +inline_always_ Stream main_stream(uintptr_t start_offset) { + return {main_ro(), start_offset}; +} + +} // namespace megaton::patch diff --git a/lib/include/megaton/prelude.h b/lib/include/megaton/prelude.h index 0a112e0..ff3d534 100644 --- a/lib/include/megaton/prelude.h +++ b/lib/include/megaton/prelude.h @@ -52,6 +52,8 @@ typedef size_t usize; #define inline_never_ __attribute__((noinline)) /** #[inline(always)] */ #define inline_always_ __attribute__((always_inline)) static inline +/** #[inline(always)] for a member function*/ +#define inline_member_ __attribute__((always_inline)) inline /** -> ! */ #define noreturn_ __attribute__((noreturn)) void diff --git a/lib/src/exl/LICENSE.md b/lib/src/exl/LICENSE similarity index 100% rename from lib/src/exl/LICENSE.md rename to lib/src/exl/LICENSE diff --git a/lib/src/exl/hook/nx64/hook_impl.cpp b/lib/src/exl/hook/nx64/hook_impl.cpp index 34ab548..3fa97a9 100644 --- a/lib/src/exl/hook/nx64/hook_impl.cpp +++ b/lib/src/exl/hook/nx64/hook_impl.cpp @@ -26,6 +26,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include "megaton/__priv/mirror.h" #include #define __STDC_FORMAT_MACROS #include @@ -33,8 +34,8 @@ #include -#include -#include +#include +#include #define __attribute __attribute__ #define aligned(x) __aligned__(x) @@ -50,12 +51,18 @@ #define __atomic_increase(p) __sync_add_and_fetch(p, 1) #define __sync_cmpswap(p, v, n) __sync_bool_compare_and_swap(p, v, n) +#ifndef MEGATON_JIT_SIZE +#define MEGATON_JIT_SIZE 0x1000 +#endif +static_assert(align_up_(MEGATON_JIT_SIZE, PAGE_SIZE) == MEGATON_JIT_SIZE, + "JIT_SIZE is not aligned"); + namespace exl::hook::nx64 { namespace { // Hooking constants -constexpr size_t HookPoolSize = setting::JitSize; +constexpr size_t HookPoolSize = MEGATON_JIT_SIZE; constexpr s64 MaxInstructions = 5; constexpr size_t TrampolineSize = MaxInstructions * 10; constexpr u64 HookMax = HookPoolSize / (TrampolineSize * sizeof(uint32_t)); @@ -66,7 +73,7 @@ typedef uint32_t HookPool[HookMax][TrampolineSize]; static_assert(sizeof(HookPool) <= HookPoolSize, ""); typedef uint32_t* __restrict* __restrict instruction; -typedef struct { +typedef struct context { struct fix_info { uint32_t* bprx; uint32_t* bprw; @@ -233,8 +240,9 @@ bool __fix_cond_comp_test_branch(instruction inprwp, instruction inprxp, 0xfff8001fu; // 0b11111111111110000000000000011111 constexpr uint32_t mask2 = 0x7f000000u; // 0b01111111000000000000000000000000 - constexpr uint32_t op_tbz = 0x36000000u; // 0b00110110000000000000000000000000 - // "tbz" Rt, BIT_NUM, ADDR_PCREL14 + constexpr uint32_t op_tbz = + 0x36000000u; // 0b00110110000000000000000000000000 + // "tbz" Rt, BIT_NUM, ADDR_PCREL14 constexpr uint32_t op_tbnz = 0x37000000u; // 0b00110111000000000000000000000000 "tbnz" Rt, BIT_NUM, // ADDR_PCREL14 @@ -605,9 +613,9 @@ void __fix_instructions(uint32_t* __restrict inprw, uint32_t* __restrict inprx, //------------------------------------------------------------------------- -JIT_CREATE(s_HookJit, setting::JitSize); +make_jit_(s_HookJit, MEGATON_JIT_SIZE); -void Initialize() { s_HookJit.Initialize(); } +void Initialize() { s_HookJit.init(); } //------------------------------------------------------------------------- @@ -621,8 +629,8 @@ static void AllocForTrampoline(uint32_t** rx, uint32_t** rw) { panic_("failed to allocate trampoline: max hook limit reached"); } - HookPool* rwptr = (HookPool*)s_HookJit.GetRw(); - HookPool* rxptr = (HookPool*)s_HookJit.GetRo(); + HookPool* rwptr = (HookPool*)s_HookJit.rw_start(); + HookPool* rxptr = (HookPool*)s_HookJit.ro_start(); *rw = (*rwptr)[i]; *rx = (*rxptr)[i]; } @@ -642,18 +650,19 @@ static bool HookFuncImpl(void* const symbol, void* const replace, auto pc_offset = static_cast(__intval(replace) - __intval(symbol)) >> 2; if (llabs(pc_offset) >= (mask >> 1)) { - const util::RwPages ctrl((uintptr_t)original, 5 * sizeof(uint32_t)); + const megaton::__priv::Mirror ctrl((uintptr_t)original, + 5 * sizeof(uint32_t)); int32_t count = (reinterpret_cast(original + 2) & 7u) != 0u ? 5 : 4; - original = (u32*)ctrl.GetRw(); + original = (u32*)ctrl.rw_start(); if (rxtrampoline) { if (TrampolineSize < count * 10u) { return false; } // if - __fix_instructions(original, (u32*)ctrl.GetRo(), count, + __fix_instructions(original, (u32*)ctrl.ro_start(), count, rwtrampoline, rxtrampoline); } // if @@ -666,15 +675,16 @@ static bool HookFuncImpl(void* const symbol, void* const replace, *reinterpret_cast(original + 2) = __intval(replace); __flush_cache(symbol, 5 * sizeof(uint32_t)); } else { - const util::RwPages ctrl((uintptr_t)original, 1 * sizeof(uint32_t)); + const megaton::__priv::Mirror ctrl((uintptr_t)original, + 1 * sizeof(uint32_t)); - original = (u32*)ctrl.GetRw(); + original = (u32*)ctrl.rw_start(); if (rwtrampoline) { if (TrampolineSize < 1u * 10u) { return false; } // if - __fix_instructions(original, (u32*)ctrl.GetRo(), 1, rwtrampoline, + __fix_instructions(original, (u32*)ctrl.ro_start(), 1, rwtrampoline, rxtrampoline); } // if @@ -705,7 +715,7 @@ uintptr_t Hook(uintptr_t hook, uintptr_t callback, bool do_trampoline) { panic_("HookFuncImpl failed"); } - s_HookJit.Flush(); + s_HookJit.flush(); return (uintptr_t)rxtrampoline; } diff --git a/lib/src/exl/hook/nx64/inline_impl.cpp b/lib/src/exl/hook/nx64/inline_impl.cpp index 19d0905..e42fee9 100644 --- a/lib/src/exl/hook/nx64/inline_impl.cpp +++ b/lib/src/exl/hook/nx64/inline_impl.cpp @@ -4,16 +4,26 @@ #include #include -#include -#include #include +#include +#include + +/** Size of inline JIT memory. */ +#ifndef MEGATON_INLINE_JIT_SIZE +#define MEGATON_INLINE_JIT_SIZE 0x1000 +#endif + namespace exl::hook::nx64 { /* Size of stack to reserve for the context. Adjust this along with * CTX_STACK_SIZE in inline_asm.s */ static constexpr int CtxStackSize = 0x100; +static constexpr usize InlinePoolSize = MEGATON_INLINE_JIT_SIZE; +static_assert(align_up_(InlinePoolSize, PAGE_SIZE) == InlinePoolSize, + "InlinePoolSize is not aligned"); + namespace reg = exl::armv8::reg; namespace inst = exl::armv8::inst; @@ -23,9 +33,10 @@ struct Entry { }; static constexpr size_t InlinePoolCount = - setting::InlinePoolSize / sizeof(Entry); + MEGATON_INLINE_JIT_SIZE / sizeof(Entry); + +make_jit_(s_InlineHookJit, MEGATON_INLINE_JIT_SIZE); -JIT_CREATE(s_InlineHookJit, setting::InlinePoolSize); static size_t s_EntryIndex = 0; extern "C" { @@ -37,14 +48,14 @@ static uintptr_t GetImpl() { } static const Entry* GetEntryRx() { - return reinterpret_cast(s_InlineHookJit.GetRo()); + return reinterpret_cast(s_InlineHookJit.ro_start()); } static Entry* GetEntryRw() { - return reinterpret_cast(s_InlineHookJit.GetRw()); + return reinterpret_cast(s_InlineHookJit.rw_start()); } -void InitializeInline() { s_InlineHookJit.Initialize(); } +void InitializeInline() { s_InlineHookJit.init(); } void HookInline(uintptr_t hook, uintptr_t callback) { /* Ensure enough space in the pool. */ @@ -82,6 +93,6 @@ void HookInline(uintptr_t hook, uintptr_t callback) { entryRw->m_Callback = callback; /* Finally, flush caches to have RX region to be consistent. */ - s_InlineHookJit.Flush(); + s_InlineHookJit.flush(); } } // namespace exl::hook::nx64 diff --git a/lib/src/exl/util/sys/cur_proc_handle.hpp b/lib/src/exl/util/sys/cur_proc_handle.hpp deleted file mode 100644 index 5359071..0000000 --- a/lib/src/exl/util/sys/cur_proc_handle.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -namespace exl::util::proc_handle { -Handle Get(); -} diff --git a/lib/src/exl/util/sys/mem_layout.cpp b/lib/src/exl/util/sys/mem_layout.cpp deleted file mode 100644 index 87884bf..0000000 --- a/lib/src/exl/util/sys/mem_layout.cpp +++ /dev/null @@ -1,182 +0,0 @@ -extern "C" { -#include -#include -} - -/* #include */ -#include - -/* Provided by linkerscript, the start of our executable. */ -extern "C" { -extern char __module_start; -} - -namespace exl::util { - -static void FindModules() { - /* Setup loop, starting address zero. */ - MemoryInfo meminfo{}; - u32 pageinfo; - - enum class State { - LookingForCodeStatic, - ExpectingRodata, - ExpectingData, - } state = State::LookingForCodeStatic; - int nextModIdx = 0; - - uintptr_t offset = 0; - uintptr_t prevOffset = 0; - util::ModuleInfo curModInfo{}; - - /* Utility to reset state. */ - auto reset = [&curModInfo, &state] { - curModInfo = {}; - state = State::LookingForCodeStatic; - }; - - do { - /* Ensure we don't find too many modules. */ - if (util::mem_layout::s_MaxModules <= nextModIdx) { - panic_("Too many static modules."); - } - - prevOffset = offset; - - /* Query next range. */ - if (R_FAILED(svcQueryMemory(&meminfo, &pageinfo, - meminfo.addr + meminfo.size))) { - panic_("svcQueryMemory failed."); - } - - /* Setup variables for state machine. */ - u32 memtype = meminfo.type & MemState_Type; - offset = meminfo.addr; - - switch (state) { - case State::LookingForCodeStatic: { - if (memtype != MemType_CodeStatic || meminfo.perm != Perm_Rx) { - /* No module here, keep going... */ - continue; - } - - /* Store text/total info and move to next state. */ - curModInfo.m_Total.m_Start = meminfo.addr; - curModInfo.m_Text.m_Start = meminfo.addr; - curModInfo.m_Text.m_Size = meminfo.size; - state = State::ExpectingRodata; - break; - } - case State::ExpectingRodata: { - if (memtype != MemType_CodeStatic || meminfo.perm != Perm_R) { - /* Not a proper module, reset. */ - reset(); - continue; - } - - /* Store rodata range and move to next state. */ - curModInfo.m_Rodata.m_Start = meminfo.addr; - curModInfo.m_Rodata.m_Size = meminfo.size; - state = State::ExpectingData; - break; - } - case State::ExpectingData: { - if (memtype != MemType_CodeMutable || meminfo.perm != Perm_Rw) { - /* Not a proper module, reset. */ - reset(); - continue; - } - - auto& total = curModInfo.m_Total; - auto& data = curModInfo.m_Data; - - /* Assign the data range and finish total range. */ - data.m_Start = meminfo.addr; - data.m_Size = meminfo.size; - total.m_Size = data.GetEnd() - total.m_Start; - - /* This only needs to be determined at runtime if we are in an - * application. */ - if (total.m_Start == (uintptr_t)&__module_start) - util::mem_layout::s_SelfModuleIdx = nextModIdx; - - /* Store built module info. */ - impl::mem_layout::s_ModuleInfos[nextModIdx] = curModInfo; - nextModIdx++; - util::mem_layout::s_ModuleCount = nextModIdx; - - /* Back to initial state. */ - reset(); - break; - } - default: { - unreachable_(); - } - } - - /* Exit once we've wrapped the address space. */ - } while (offset >= prevOffset); - - /* Ensure we found a valid self index and module count. */ - assert_(util::mem_layout::s_SelfModuleIdx != -1); - assert_(util::mem_layout::s_SelfModuleIdx < util::mem_layout::s_MaxModules); -} - -static Result TryGetAddressFromInfo(InfoType type, uintptr_t* ptr) { - return svcGetInfo(static_cast(ptr), type, CUR_PROCESS_HANDLE, 0); -} - -static uintptr_t GetAddressFromInfo(InfoType type) { - uintptr_t addr; - if (R_FAILED(TryGetAddressFromInfo(type, &addr))) { - panic_("svcGetInfo failed."); - } - return addr; -} - -static void InferAslrAndStack() { - Result rc = svcUnmapMemory((void*)0xFFFFFFFFFFFFE000UL, - (void*)0xFFFFFE000UL, 0x1000); - if (R_VALUE(rc) == KERNELRESULT(InvalidMemoryState)) { - // Invalid src-address error means that a valid 36-bit address was - // rejected. Thus we are 32-bit. - util::mem_layout::s_Aslr = util::Range(0x200000ull, 0x100000000ull); - util::mem_layout::s_Stack = util::Range(0x200000ull, 0x40000000ull); - } else if (R_VALUE(rc) == KERNELRESULT(InvalidMemoryRange)) { - // Invalid dst-address error means our 36-bit src-address was valid. - // Thus we are 36-bit. - util::mem_layout::s_Aslr = util::Range(0x8000000ull, 0x1000000000ull); - util::mem_layout::s_Stack = util::Range(0x8000000ull, 0x80000000ull); - } else { - // Wat. - panic_nx_("failed infer aslr and stack", - MAKERESULT(Module_Libnx, LibnxError_WeirdKernel)); - } -} - -static void FindRegions() { - util::mem_layout::s_Alias = - util::Range(GetAddressFromInfo(InfoType_AliasRegionAddress), - GetAddressFromInfo(InfoType_AliasRegionSize)); - - util::mem_layout::s_Heap = - util::Range(GetAddressFromInfo(InfoType_HeapRegionAddress), - GetAddressFromInfo(InfoType_HeapRegionSize)); - - if (R_FAILED(TryGetAddressFromInfo(InfoType_AslrRegionAddress, - &util::mem_layout::s_Aslr.m_Start)) || - R_FAILED(TryGetAddressFromInfo(InfoType_AslrRegionSize, - &util::mem_layout::s_Aslr.m_Size)) || - R_FAILED(TryGetAddressFromInfo(InfoType_StackRegionAddress, - &util::mem_layout::s_Stack.m_Start)) || - R_FAILED(TryGetAddressFromInfo(InfoType_StackRegionSize, - &util::mem_layout::s_Stack.m_Size))) { - InferAslrAndStack(); - } -} - -void impl::InitMemLayout() { - FindModules(); - FindRegions(); -} -}; // namespace exl::util diff --git a/lib/src/exl/util/sys/rw_pages.cpp b/lib/src/exl/util/sys/rw_pages.cpp deleted file mode 100644 index 25c5ace..0000000 --- a/lib/src/exl/util/sys/rw_pages.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include - -#include -#include - -extern "C" { -#include -#include -#include -#include -} - -#include - -#include "cur_proc_handle.hpp" - -namespace exl::util { - -template -static void ForEachMemRange(Callback callback, uintptr_t start_address, - size_t length) { - const uintptr_t end = start_address + length; - /* Setup variables. */ - MemoryInfo meminfo{.addr = start_address}; - u32 pageinfo; - - do { - /* Query next range. */ - if (R_FAILED(svcQueryMemory(&meminfo, &pageinfo, - meminfo.addr + meminfo.size))) { - panic_("svcQueryMemory failed."); - } - - /* Calculate offset into the range we are mapping. */ - uintptr_t offset = - std::max(meminfo.addr, start_address) - start_address; - /* Determine the address we will be working on. */ - uintptr_t current_address = start_address + offset; - /* Determine the length of the range we will be working on. */ - uintptr_t current_length = - std::min(end, meminfo.addr + meminfo.size) - current_address; - - /* Call provided callback. */ - callback(current_address, current_length, offset); - - /* Exit once we've mapped enough pages. */ - } while ((meminfo.addr + meminfo.size) < end); -} - -RwPages::RwPages(uintptr_t ro, size_t size) { - /* Initialize the claim with what we know. */ - m_Claim = { - .m_Ro = ro, - .m_Size = size, - }; - - /* Get const ref to claim. */ - const auto& claim = GetClaim(); - - /* Find space for the corresponding rw region. */ - uintptr_t alignedRw = (uintptr_t)virtmemFindAslr(claim.GetAlignedSize(), 0); - assert_(alignedRw != 0); - - /* Reserve rw region. */ - auto reserve = - virtmemAddReservation((void*)alignedRw, claim.GetAlignedSize()); - assert_(reserve != NULL); - m_Claim.m_RwReserve = reserve; - - auto procHandle = proc_handle::Get(); - - /* Iterate through every range and map. */ - ForEachMemRange( - [alignedRw, procHandle](uintptr_t address, uintptr_t size, - uintptr_t offset) { - void* rw = (void*)(alignedRw + offset); - u64 ro = address; - - if (R_FAILED(svcMapProcessMemory(rw, procHandle, ro, size))) { - panic_("svcMapProcessMemory failed."); - } - }, - claim.GetAlignedRo(), claim.GetAlignedSize()); - - /* Setup RW pointer to match same physical location of RX. */ - m_Claim.m_Rw = alignedRw + (ro - claim.GetAlignedRo()); - - /* Ensure the data at the different mapping is the same. */ - assert_(memcmp((void*)claim.m_Ro, (void*)claim.m_Rw, size) == 0); -} - -void RwPages::Flush() { - const auto& claim = GetClaim(); - armDCacheFlush((void*)claim.GetAlignedRw(), claim.GetAlignedSize()); - armICacheInvalidate((void*)claim.GetAlignedRw(), claim.GetAlignedSize()); -} - -RwPages::~RwPages() { - /* Only unclaim if this is the owner. */ - if (!m_Owner) - return; - - const auto& claim = GetClaim(); - - /* Flush data. */ - armDCacheFlush((void*)claim.m_Rw, claim.m_Size); - armICacheInvalidate((void*)claim.m_Ro, claim.m_Size); - - auto procHandle = proc_handle::Get(); - - /* Iterate through every range and unmap. */ - ForEachMemRange( - [&claim, procHandle](uintptr_t address, uintptr_t size, - uintptr_t offset) { - void* rw = reinterpret_cast(claim.GetAlignedRw() + offset); - u64 ro = address; - - if (R_FAILED(svcUnmapProcessMemory(rw, procHandle, ro, size))) { - panic_("svcUnmapProcessMemory failed."); - } - }, - m_Claim.GetAlignedRo(), m_Claim.GetAlignedSize()); - - /* Free RW reservation. */ - virtmemRemoveReservation(claim.m_RwReserve); -} -}; // namespace exl::util diff --git a/lib/src/megaton/__priv/mirror.cpp b/lib/src/megaton/__priv/mirror.cpp new file mode 100644 index 0000000..f24d4ab --- /dev/null +++ b/lib/src/megaton/__priv/mirror.cpp @@ -0,0 +1,122 @@ +/* + * This file is part of exlaunch, adapted for megaton project + * + * exlaunch is licensed under GPLv2 + */ +#include + +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +#include +#include + +namespace megaton::__priv { + +/** Map or unmap the memory in the given range. */ +static void handle_mapping( + /** Page-aligned start of the read-only region. */ + uintptr_t ro_start_aligned, + /** Page-aligned start of the read-write region. */ + uintptr_t rw_start_aligned, + /** Page-aligned size of the region. */ + size_t size_aligned, + /** Current process handle. */ + Handle process, + /** Map or unmap */ + bool map) { + const uintptr_t end_aligned = ro_start_aligned + size_aligned; + + MemoryInfo meminfo{.addr = ro_start_aligned}; + u32 pageinfo; + + do { + // Query next range + if (R_FAILED(svcQueryMemory(&meminfo, &pageinfo, + meminfo.addr + meminfo.size))) { + panic_("mirror: svcQueryMemory failed"); + } + + // Calculate offset into the range we are mapping. + // Force the start to be at least the aligned start if for some reason + // it is not + uintptr_t offset = + std::max(meminfo.addr, ro_start_aligned) - ro_start_aligned; + // Determine the address we will be working on. + uintptr_t ro_start = ro_start_aligned + offset; + void* rw_start = (void*)(rw_start_aligned + offset); + /* Determine the size of this range to map/unmap. */ + uintptr_t size = + std::min(end_aligned, meminfo.addr + meminfo.size) - ro_start; + + if (map) { + if (R_FAILED( + svcMapProcessMemory(rw_start, process, ro_start, size))) { + panic_("mirror: svcMapProcessMemory failed"); + } + } else { + if (R_FAILED( + svcUnmapProcessMemory(rw_start, process, ro_start, size))) { + panic_("mirror: svcUnmapProcessMemory failed"); + } + } + } while ((meminfo.addr + meminfo.size) < end_aligned); +} + +Mirror::Mirror(uintptr_t start, size_t size) { + m.ro_start = start; + m.size = size; + + auto size_aligned = m.size_aligned(); + + // Find a page for the RW region and reserve it + uintptr_t rw_start_aligned = (uintptr_t)virtmemFindAslr(size_aligned, 0); + assert_(rw_start_aligned != 0); + auto reserve = virtmemAddReservation((void*)rw_start_aligned, size_aligned); + assert_(reserve != NULL); + m.rw_reserve = reserve; + + // Get the process handle for mapping memory + auto process = megaton::__priv::current_process(); + + auto ro_start_aligned = m.ro_start_aligned(); + + handle_mapping(ro_start_aligned, rw_start_aligned, size_aligned, process, + true); + + // Setup RW pointer to match same unaligned location of RO. + m.rw_start = rw_start_aligned + (start - ro_start_aligned); + + // Ensure the mapping worked + assert_(memcmp((void*)m.ro_start, (void*)m.rw_start, size) == 0); +} + +void Mirror::flush() { + auto size_aligned = m.size_aligned(); + armDCacheFlush((void*)m.rw_start_aligned(), size_aligned); + armICacheInvalidate((void*)m.ro_start_aligned(), size_aligned); +} + +Mirror::~Mirror() { + /* Only uninit if this is the owner. */ + if (!m.rw_reserve) + return; + + flush(); + + auto process = megaton::__priv::current_process(); + + handle_mapping(m.ro_start_aligned(), m.rw_start_aligned(), m.size_aligned(), + process, false); + + // Free reservation of the read-write region + virtmemRemoveReservation(m.rw_reserve); +} +}; // namespace megaton::__priv diff --git a/lib/src/exl/util/sys/cur_proc_handle.cpp b/lib/src/megaton/__priv/proc_handle.cpp similarity index 67% rename from lib/src/exl/util/sys/cur_proc_handle.cpp rename to lib/src/megaton/__priv/proc_handle.cpp index 5f38848..bf54410 100644 --- a/lib/src/exl/util/sys/cur_proc_handle.cpp +++ b/lib/src/megaton/__priv/proc_handle.cpp @@ -1,3 +1,9 @@ +/* + * This file is part of exlaunch, adapted for megaton project + * + * exlaunch is licensed under GPLv2 + */ + #include #include @@ -9,15 +15,16 @@ extern "C" { #include } -#include "cur_proc_handle.hpp" - -namespace exl::util::proc_handle { +#include -namespace { +namespace megaton::__priv { -Handle s_Handle = INVALID_HANDLE; +static const u32 s_send_handle_msg[4] = {0x00000000, 0x80000000, 0x00000002, + CUR_PROCESS_HANDLE}; +static Handle s_handle = INVALID_HANDLE; -void ReceiveProcessHandleThreadMain(void* session_handle_ptr) { +/** thread that will receive the proc handle */ +static noreturn_ recv_handle_thread_main(void* session_handle_ptr) { // Convert the argument to a handle we can use. Handle session_handle = (Handle)(uintptr_t)session_handle_ptr; @@ -30,7 +37,7 @@ void ReceiveProcessHandleThreadMain(void* session_handle_ptr) { } // Set the process handle. - s_Handle = ((u32*)armGetTls())[3]; + s_handle = ((u32*)armGetTls())[3]; // Close the session. svcCloseHandle(session_handle); @@ -43,7 +50,7 @@ void ReceiveProcessHandleThreadMain(void* session_handle_ptr) { ; } -void GetViaIpcTrick(void) { +static void get_via_ipc_trick(void) { alignas(PAGE_SIZE) u8 temp_thread_stack[0x1000]; // Create a new session to transfer our process handle to ourself @@ -55,7 +62,7 @@ void GetViaIpcTrick(void) { // Create a new thread to receive our handle. Handle thread_handle; if (R_FAILED(svcCreateThread( - &thread_handle, (void*)&ReceiveProcessHandleThreadMain, + &thread_handle, (void*)&recv_handle_thread_main, (void*)(uintptr_t)server_handle, temp_thread_stack + sizeof(temp_thread_stack), 0x20, 2))) { panic_("svcCreateThread failed."); @@ -67,10 +74,7 @@ void GetViaIpcTrick(void) { } // Send the message. - static const u32 SendProcessHandleMessage[4] = { - 0x00000000, 0x80000000, 0x00000002, CUR_PROCESS_HANDLE}; - memcpy(armGetTls(), SendProcessHandleMessage, - sizeof(SendProcessHandleMessage)); + memcpy(armGetTls(), s_send_handle_msg, sizeof(s_send_handle_msg)); svcSendSyncRequest(client_handle); // Close the session handle. @@ -85,28 +89,28 @@ void GetViaIpcTrick(void) { svcCloseHandle(thread_handle); } -Result GetViaMesosphere() { +static Result get_via_mesosphere() { u64 handle; Result r = svcGetInfo(&handle, 65001 /*InfoType_MesosphereCurrentProcess*/, INVALID_HANDLE, 0); if (R_FAILED(r)) { return r; } - s_Handle = handle; + s_handle = handle; return 0; } -} // namespace -Handle Get() { - if (s_Handle == INVALID_HANDLE) { +Handle current_process() { + if (s_handle == INVALID_HANDLE) { /* Try to ask mesosphere for our process handle. */ - Result r = GetViaMesosphere(); + Result r = get_via_mesosphere(); /* Fallback to an IPC trick if mesosphere is old/not present. */ - if (R_FAILED(r)) - GetViaIpcTrick(); + if (R_FAILED(r)) { + get_via_ipc_trick(); + } } - return s_Handle; + return s_handle; } -}; // namespace exl::util::proc_handle +}; // namespace megaton::__priv diff --git a/lib/src/megaton/init.cpp b/lib/src/megaton/init.cpp index 63b1ec5..f750960 100644 --- a/lib/src/megaton/init.cpp +++ b/lib/src/megaton/init.cpp @@ -1,8 +1,7 @@ -#include +#include +#include #include -#include -#include extern "C" { /** @@ -29,18 +28,19 @@ void __init_array(void) { } // from libnx -// virtmem needed for RwPages +// virtmem needed for mapping writable memory to read-only memory // the setup is not in libnx's header for some reason void virtmemSetup(void); void __megaton_lib_init() { - exl::util::impl::InitMemLayout(); virtmemSetup(); - exl::patch::impl::InitPatcherImpl(); + megaton::module::init_layout(); + megaton::patch::init(); __init_array(); exl::hook::Initialize(); } +// TODO: this can probably be removed with rtld/reloc void __megaton_rtld_init() {} } diff --git a/lib/src/megaton/module_layout.cpp b/lib/src/megaton/module_layout.cpp new file mode 100644 index 0000000..7b29d30 --- /dev/null +++ b/lib/src/megaton/module_layout.cpp @@ -0,0 +1,123 @@ +#include +extern "C" { +#include +#include +} +#include + +namespace megaton::module { + +static constexpr u32 MAX_MODULES = 13; +static std::array s_info_array; +static u32 s_count = 0; +static u32 s_self_idx = MAX_MODULES + 1; + +u32 count() { return s_count; } + +const Info& info_at(u32 index) { + assert_(index < s_count); + return s_info_array[index]; +} +const Info& sdk_info() { + // SDK is always placed last in our impl + return s_info_array[s_count - 1]; +} +const Info& self_info() { return s_info_array[s_self_idx]; } + +/* Provided by linker script, the start of our executable. */ +extern "C" { +extern char __module_start; +} + +/* + * This initialization is adapted from exlaunch + */ +void init_layout() { + enum class State { + /** Looking for code (text) section. */ + Text, + /** Expecting rodata section. */ + Rodata, + /** Expecting data section. */ + Data, + } state = State::Text; + + MemoryInfo meminfo{}; + u32 pageinfo; + u32 next_id = 0; + uintptr_t offset = 0; + uintptr_t prev_offset = 0; + Info builder{}; + + do { + if (MAX_MODULES <= next_id) { + panic_("init_layout: too many static modules"); + } + + prev_offset = offset; + + // Query next range. + if (R_FAILED(svcQueryMemory(&meminfo, &pageinfo, + meminfo.addr + meminfo.size))) { + panic_("init_layout: svcQueryMemory failed"); + } + + u32 memtype = meminfo.type & MemState_Type; + offset = meminfo.addr; + + switch (state) { + case State::Text: { + if (memtype != MemType_CodeStatic || meminfo.perm != Perm_Rx) { + // No module here, keep going... + continue; + } + + builder.text().set(meminfo.addr, meminfo.size); + state = State::Rodata; + break; + } + case State::Rodata: { + if (memtype != MemType_CodeStatic || meminfo.perm != Perm_R) { + /* Not a proper module, reset. */ + state = State::Text; + continue; + } + + builder.rodata().set(meminfo.addr, meminfo.size); + state = State::Data; + break; + } + case State::Data: { + if (memtype != MemType_CodeMutable || meminfo.perm != Perm_Rw) { + /* Not a proper module, reset. */ + state = State::Text; + continue; + } + + builder.data().set(meminfo.addr, meminfo.size); + + if (builder.start() == (uintptr_t)&__module_start) { + s_self_idx = next_id; + } + + // Store built module info. + s_info_array[next_id++] = builder; + + // Back to initial state. + state = State::Text; + break; + } + default: { + unreachable_(); + } + } + + // Exit once we've wrapped the address space. + } while (offset >= prev_offset); + + s_count = next_id; + // Ensure we found a valid self index and module count. + assert_(s_self_idx < s_count); +} + +} // namespace megaton::module diff --git a/lib/src/megaton/patcher.cpp b/lib/src/megaton/patcher.cpp new file mode 100644 index 0000000..ef83d63 --- /dev/null +++ b/lib/src/megaton/patcher.cpp @@ -0,0 +1,41 @@ +extern "C" { +#include +} + +#include +#include +#include + +namespace megaton::patch { +static __priv::AlignedStorage s_main_rx; + +const __priv::Mirror& main_ro() { return s_main_rx.reference(); } + +void init() { + auto& mod = module::main_info(); + // map the text and rodata sections + auto start = mod.start(); + auto size = mod.text().size() + mod.rodata().size(); + s_main_rx.construct(start, size); +} + +void Stream::flush() { + if (rw_start_addr == rw_current_addr) { + // didn't write anything, skip + return; + } + // find the region to flush + void* ro = (void*)ro_start_addr; + void* rw = (void*)rw_start_addr; + auto size = rw_current_addr - rw_start_addr; + + /* Flush data/instructions. */ + armDCacheFlush(rw, size); + armICacheInvalidate(ro, size); + + // Reset start position for next flush + rw_start_addr += size; + ro_start_addr += size; +} + +} // namespace megaton::patch