diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp index cf940e3b..0baf0b56 100644 --- a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp @@ -567,6 +567,11 @@ namespace ams::ldr { out->nso_size[i] = std::max(out->nso_size[i], rw_end); out->nso_size[i] += static_cast(ctx.headers[i].bss_size); + /* Reserve hook arena memory past pcv's bss. */ + if (g_is_pcv && i == ctx.main_nso_idx) { + out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize) + hoc::pcv::PcvDataArenaSize; + } + const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1); R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso()); R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso()); @@ -691,6 +696,9 @@ namespace ams::ldr { Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) { const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0; + const size_t module_size = static_cast(nso_header->rw_dst_offset) + util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize); + const size_t arena_size = (nso_size > module_size) ? (nso_size - module_size) : 0; + /* Map and read data from file. */ { /* Map the process memory. */ @@ -730,17 +738,15 @@ namespace ams::ldr { /* Apply PCV and PTM patches */ if (g_is_pcv) { - /* Hand the pcv patcher the zero-padding after .text (memset to 0 above, mapped - ReadExecute below) as a code cave for out-of-line trampolines. text_dst_offset - is validated == 0. Clamp to the page-aligned .text mapping so the cave can only - live inside the RX region. */ - const size_t pcv_text_end = static_cast(nso_header->text_size); - const size_t pcv_rx_end = util::AlignUp(pcv_text_end, os::MemoryPageSize); - const size_t pcv_ro_start = static_cast(nso_header->ro_dst_offset); - const size_t pcv_cave_end = (pcv_rx_end < pcv_ro_start) ? pcv_rx_end : pcv_ro_start; - hoc::pcv::g_pcv_cave = map_address + pcv_text_end; - hoc::pcv::g_pcv_cave_size = (pcv_cave_end > pcv_text_end) ? (pcv_cave_end - pcv_text_end) : 0; - hoc::pcv::Patch(map_address, nso_size); + const size_t text_end = static_cast(nso_header->text_size); + const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize); + const size_t ro_start = static_cast(nso_header->ro_dst_offset); + const size_t cave_end = (rx_end < ro_start) ? rx_end : ro_start; + const uintptr_t cave = map_address + text_end; + const size_t cave_size = (cave_end > text_end) ? (cave_end - text_end) : 0; + + /* module_size is used rather than nso_size to exclude the extra data section. */ + hoc::pcv::Patch(map_address, module_size, cave, cave_size, nso_address, arena_size ? (map_address + module_size) : 0); } if (g_is_ptm) { @@ -751,7 +757,7 @@ namespace ams::ldr { /* Set permissions. */ const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize); const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize); - const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize); + const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize) + arena_size; if (text_size) { const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads); R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute)); @@ -780,8 +786,7 @@ namespace ams::ldr { const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0; const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i]; - R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i, - process_info->nso_address[i], process_info->nso_size[i], map_size)); + R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i, process_info->nso_address[i], process_info->nso_size[i], map_size)); } /* Load arguments, if present. */ @@ -809,8 +814,9 @@ namespace ams::ldr { Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) { /* Append extra .bss for 64LUT */ + /* TODO: REMOVE THIS. */ if (g_is_pcv && ctx.main_nso_idx >= 0) { - g_nso_headers[ctx.main_nso_idx].bss_size += static_cast(hoc::HocPcvScratchSize); + g_nso_headers[ctx.main_nso_idx].bss_size += static_cast(hoc::pcv::HocPcvScratchSize); } /* Get CreateProcessParameter. */ diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp index 55a98b6d..8a9ef004 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp @@ -40,37 +40,40 @@ *(offset) = value; namespace ams::ldr { - R_DEFINE_ERROR_RESULT(OutOfRange, 1000); - R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001); - R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002); - R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003); - R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004); - R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005); - R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006); - R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007); - R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008); - R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009); - R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010); - R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011); - R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012); - R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013); - R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014); - R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015); - R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016); - R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017); - R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018); - R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019); - R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020); - R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021); - R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022); - R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023); + R_DEFINE_ERROR_RESULT(OutOfRange, 1000); + R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001); + R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002); + R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003); + R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004); + R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005); + R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006); + R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007); + R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008); + R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009); + R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010); + R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011); + R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012); + R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013); + R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014); + R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015); + R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016); + R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017); + R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018); + R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019); + R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020); + R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021); + R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022); + R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023); + R_DEFINE_ERROR_RESULT(HookArenaOutOfMemory, 1024); + R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1025); + R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1026); + R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1027); + R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1028); + R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1029); + R_DEFINE_ERROR_RESULT(HookUnavailable, 1030); } namespace ams::ldr::hoc { - /* Extra pcv .bss */ - constexpr size_t HocPcvScratchSize = 0x2000; - constexpr size_t HocBusFreqBufOffset = 0x1000; /* start of the SOC bus region */ - template struct PatcherEntry { using patternFn = bool(*)(Pointer *ptr); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp index d14ee91a..75068c45 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp @@ -107,6 +107,7 @@ namespace ams::ldr::hoc::pcv { } else { marikoCpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); } + u32 eristaGpuDvfsMaxFreq; switch (C.eristaGpuUV) { case 0: @@ -176,9 +177,11 @@ namespace ams::ldr::hoc::pcv { R_DISCARD(SmcCopyToIram(LoadMagicAddress, &hocMagic, sizeof(hocMagic))); } - void Patch(uintptr_t mapped_nso, size_t nso_size) { + void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena) { SafetyCheck(); + Hooks().Initialize(mapped_nso, nso_address, cave, cave_size, data_arena); + bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); if (isMariko) { mariko::Patch(mapped_nso, nso_size); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp index a4586c94..056ca970 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp @@ -24,6 +24,7 @@ #include "pcv_common.hpp" #include "pcv_erista.hpp" #include "pcv_mariko.hpp" +#include "pcv_hook.hpp" namespace ams::ldr::hoc::pcv { @@ -199,7 +200,11 @@ namespace ams::ldr::hoc::pcv { Result MemFreqPllmLimit(u32 *ptr); Result MemVoltHandler(u32 *ptr); // Used for Erista MEM Vdd2 + EMC Vddq or Mariko MEM Vdd2 + /* Extra pcv .bss */ + constexpr size_t HocPcvScratchSize = 0x2000; + constexpr size_t HocBusFreqBufOffset = 0x1000; /* start of the SOC bus region */ + void SafetyCheck(); - void Patch(uintptr_t mapped_nso, size_t nso_size); + void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena); } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp index b2dbceeb..4f9f22f7 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp @@ -294,7 +294,6 @@ namespace ams::ldr::hoc::pcv { bool secondMatch = (ins2 & StpRegsImmMask) == (cmp2 & StpRegsImmMask); - constexpr u32 MovMask = ~((1u << 5) - 1u); bool thirdMatch = (ins3 & MovMask) == (cmp3 & MovMask); @@ -368,4 +367,25 @@ namespace ams::ldr::hoc::pcv { return (ins1 & ClearImm19) == (ins2 & ClearImm19); }; + inline bool AsmIsFramePush(u32 ins) { + constexpr u32 FramePushMask = 0xFFC07FFF; + constexpr u32 FramePushValue = 0xA9807BFD; + return (ins & FramePushMask) == FramePushValue; + } + + inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) { + for (u32 i = 0; i <= margin; ++i) { + u32 *candidate = ptr - i; + if (candidate < nsoStart) { + break; + } + + if (AsmIsFramePush(*candidate)) { + return candidate; + } + } + + return nullptr; + } + } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp new file mode 100644 index 00000000..06acacda --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp @@ -0,0 +1,364 @@ +/* + * Copyright (c) Lightos_ + * + * 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 "../oc_common.hpp" + +#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden"))) + +/* Inline asm because fuck compilers: GCC refuses to put a variable and a function in the same section. */ +/* It wants alloc+write for one and alloc+exec for the other. */ +/* You are supposed to be able to override that by writing the flags into the section name yourself, */ +/* like section("hoc_hookpayload,\"ax\",%progbits"), instead of letting GCC pick them. */ +/* But GCC takes that whole string as the name and never reads the flags out of it, so it appends the */ +/* ones it wanted anyway: the .section directive it emits ends up carrying two sets of flags, and the */ +/* assembler rejects it. The failure lands in the generated assembly, not in anything we wrote. */ +/* Therefore we must use inline assembly, which reaches the assembler exactly as written. */ +#define DEFINE_HOOK_PAYLOAD_PTR(type, name) \ + asm(".section hoc_hookpayload,\"ax\",%progbits\n" \ + ".balign 8\n" \ + ".global " #name "\n" \ + ".hidden " #name "\n" \ + #name ": .zero 8\n" \ + ".text\n"); \ + extern "C" __attribute__((visibility("hidden"))) type *name + +#define HOOK_PAYLOAD_PTR(type, name) \ + ([]() -> type * { \ + type **_hoc_pp; \ + __asm__("adr %0, " #name : "=r"(_hoc_pp)); \ + return *_hoc_pp; \ + }()) + +extern "C" const u8 __start_hoc_hookpayload[]; +extern "C" const u8 __stop_hoc_hookpayload[]; + +namespace ams::ldr::hoc::pcv { + + constexpr size_t HookPageSize = 0x1000; + constexpr size_t PcvDataArenaSize = 0x1000; + + inline s64 SignExtend(u64 value, int bits) { + const int shift = 64 - bits; + return static_cast(value << shift) >> shift; + } + + inline u32 EncodeRelBranch(u32 opc, uintptr_t site_va, uintptr_t target_va) { + const s64 delta = static_cast(target_va) - static_cast(site_va); + AMS_ABORT_UNLESS((delta & 0x3) == 0); + AMS_ABORT_UNLESS(delta >= -0x08000000 && delta <= 0x07FFFFFC); + return opc | (static_cast(delta >> 2) & 0x03FFFFFFu); + } + + inline u32 EncodeB(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x14000000u, site_va, target_va); } + inline u32 EncodeBL(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x94000000u, site_va, target_va); } + + inline u32 EncodePairSp(bool load, u32 rt1, u32 rt2, s32 imm) { + const u32 base = load ? 0xA9400000u : 0xA9000000u; + const u32 imm7 = static_cast((imm / 8) & 0x7F); + return base | (imm7 << 15) | (rt2 << 10) | (31u << 5) | rt1; + } + + inline u32 EncodeSpAdjust(bool sub, u32 imm12) { + const u32 base = sub ? 0xD1000000u : 0x91000000u; + return base | ((imm12 & 0xFFFu) << 10) | (31u << 5) | 31u; + } + + inline Result RelocateInstruction(u32 insn, uintptr_t old_site_va, uintptr_t new_site_va, u32 *out) { + const u32 top6 = insn & 0xFC000000u; + if (top6 == 0x14000000u || top6 == 0x94000000u) { + const s64 old_imm = SignExtend(static_cast(insn & 0x03FFFFFFu) << 2, 28); + const uintptr_t target = old_site_va + old_imm; + *out = EncodeRelBranch(top6, new_site_va, target); + R_SUCCEED(); + } + + R_UNLESS((insn & 0xFF000010u) != 0x54000000u, ldr::ResultHookRelocationUnsupported()); /* b.cond */ + R_UNLESS((insn & 0x7E000000u) != 0x34000000u, ldr::ResultHookRelocationUnsupported()); /* cbz/cbnz */ + R_UNLESS((insn & 0x7E000000u) != 0x36000000u, ldr::ResultHookRelocationUnsupported()); /* tbz/tbnz */ + R_UNLESS((insn & 0x1F000000u) != 0x10000000u, ldr::ResultHookRelocationUnsupported()); /* adr/adrp */ + R_UNLESS((insn & 0x3B000000u) != 0x18000000u, ldr::ResultHookRelocationUnsupported()); /* ldr literal */ + + *out = insn; + R_SUCCEED(); + } + + class HookContext { + private: + uintptr_t m_map_base = 0; /* loader-side base of pcv's NSO mapping. */ + uintptr_t m_va_base = 0; /* pcv-side base of the same memory. */ + uintptr_t m_cave = 0; /* loader-side cave base. (0 if unavailable) */ + size_t m_cave_size = 0; + uintptr_t m_payload = 0; /* loader-side base of the payload copy. */ + size_t m_payload_sz = 0; + size_t m_used = 0; + uintptr_t m_data = 0; /* loader-side data arena base, 0 if none. */ + size_t m_data_used = 0; + public: + constexpr HookContext() = default; + + void Initialize(uintptr_t map_base, uintptr_t va_base, uintptr_t cave, size_t cave_size, uintptr_t data) { + m_map_base = map_base; + m_va_base = va_base; + m_cave = cave; + m_cave_size = cave_size; + m_payload = 0; + m_payload_sz = 0; + m_used = 0; + m_data = data; + m_data_used = 0; + } + + bool IsEnabled() const { return m_cave != 0 && m_cave_size != 0; } + + Result CheckEnabled() const { + R_UNLESS(this->IsEnabled(), ldr::ResultHookUnavailable()); + R_SUCCEED(); + } + + size_t CaveSize() const { return m_cave_size; } + size_t CaveUsed() const { return m_used; } + size_t CaveFree() const { return m_cave_size > m_used ? m_cave_size - m_used : 0; } + + size_t DataSize() const { return m_data != 0 ? PcvDataArenaSize : 0; } + size_t DataUsed() const { return m_data_used; } + size_t DataFree() const { return this->DataSize() - m_data_used; } + + /* Convert loader mapped address to pcv-side address. */ + uintptr_t ToVa(const void *loader_ptr) const { + return m_va_base + (reinterpret_cast(loader_ptr) - m_map_base); + } + + uintptr_t CaveVa() const { return ToVa(reinterpret_cast(m_cave)); } + + Result CopyPayload() { + R_TRY(this->CheckEnabled()); + + const size_t size = static_cast(__stop_hoc_hookpayload - __start_hoc_hookpayload); + + /* Zero length: Linker garbage collected the payload .(happens when nothing references it) */ + /* __start_ and _stop_ don't prevent this. */ + /* Copying here would succeed at first but fail later by jumping to empty memory. */ + R_UNLESS(size != 0, ldr::ResultUninitializedPatcher()); + R_UNLESS(size <= m_cave_size, ldr::ResultHookPayloadTooLarge()); + + m_payload = m_cave; + m_payload_sz = size; + std::memcpy(reinterpret_cast(m_payload), __start_hoc_hookpayload, size); + + m_used = util::AlignUp(size, sizeof(u32)); + R_SUCCEED(); + } + + /* pcv-side address of a payload symbol's copy. */ + uintptr_t PayloadVa(const void *loader_sym) const { + const uintptr_t offset = reinterpret_cast(loader_sym) - reinterpret_cast(__start_hoc_hookpayload); + return this->ToVa(reinterpret_cast(m_payload + offset)); + } + + /* Loader-side, writable pointer to a payload variable's copy in the cave. */ + template + T *PayloadCopyOf(T &loader_sym) const { + const uintptr_t offset = reinterpret_cast(std::addressof(loader_sym)) - reinterpret_cast(__start_hoc_hookpayload); + return reinterpret_cast(m_payload + offset); + } + + /* Reserves space in the writable data arena past pcv's .bss. */ + /* Returns a zeroed, loader-side pointer. */ + /* The data arena is too far from the cave to be addressed directly by symbol, so we must store a pointer to it in the cave section. */ + template + T *DataAlloc() { + const size_t size = util::AlignUp(sizeof(T), alignof(T) > 8 ? alignof(T) : 8); + if (m_data == 0 || m_data_used + size > PcvDataArenaSize) { + return nullptr; + } + + T *p = reinterpret_cast(m_data + m_data_used); + m_data_used += size; + + std::memset(p, 0, sizeof(T)); + return p; + } + + template + T *BindData(T *&loader_sym) { + T *block = this->DataAlloc(); + if (block != nullptr) { + *this->PayloadCopyOf(loader_sym) = reinterpret_cast(this->ToVa(block)); + } + return block; + } + + /* Replaces the function entirely starting at function prologue. */ + /* Preserves the function arguments. */ + Result InstallImpl(u32 *site, const void *fn, uintptr_t *out_orig = nullptr) { + R_TRY(this->CheckEnabled()); + R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid()); + R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher()); + R_TRY(this->ValidatePayloadFn(fn)); + + if (out_orig != nullptr) { + u32 *tramp = this->AllocCode(2); + R_UNLESS(tramp != nullptr, ldr::ResultHookArenaOutOfMemory()); + + u32 relocated; + R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&tramp[0]), std::addressof(relocated))); + tramp[0] = relocated; + tramp[1] = EncodeB(this->ToVa(&tramp[1]), this->ToVa(site) + sizeof(u32)); + + *out_orig = this->ToVa(tramp); + } + + site[0] = EncodeB(this->ToVa(site), this->PayloadVa(fn)); + + R_SUCCEED(); + } + + /* Takes the same arguments as the hooked function, does not replace. */ + Result InstallIntercept(u32 *site, const void *fn) { + R_TRY(this->CheckEnabled()); + R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid()); + R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher()); + R_TRY(this->ValidatePayloadFn(fn)); + + /* x0-x7: arguments */ + /* x8: result pointer */ + /* x18: platform register */ + /* x30: return address */ + /* x29: keeps pairs clean */ + /* x9-x17: scratch */ + constexpr u32 FrameSize = 0x60; + constexpr u32 Pairs[][2] = { {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 18}, {29, 30} }; + constexpr u32 PairCount = sizeof(Pairs) / sizeof(Pairs[0]); + constexpr u32 StubWords = 1 + PairCount + 1 + PairCount + 1 + 1 + 1; + + u32 *stub = this->AllocCode(StubWords); + R_UNLESS(stub != nullptr, ldr::ResultHookArenaOutOfMemory()); + + u32 i = 0; + stub[i++] = EncodeSpAdjust(true, FrameSize); + for (u32 p = 0; p < PairCount; ++p) { + stub[i++] = EncodePairSp(false, Pairs[p][0], Pairs[p][1], static_cast(p * 16)); + } + + stub[i] = EncodeBL(this->ToVa(&stub[i]), this->PayloadVa(fn)); + ++i; + + for (u32 p = 0; p < PairCount; ++p) { + stub[i++] = EncodePairSp(true, Pairs[p][0], Pairs[p][1], static_cast(p * 16)); + } + stub[i++] = EncodeSpAdjust(false, FrameSize); + + u32 relocated; + R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&stub[i]), std::addressof(relocated))); + stub[i] = relocated; + ++i; + + stub[i] = EncodeB(this->ToVa(&stub[i]), this->ToVa(site) + sizeof(u32)); + ++i; + + AMS_ABORT_UNLESS(i == StubWords); + + site[0] = EncodeB(this->ToVa(site), this->ToVa(stub)); + R_SUCCEED(); + } + + /* Checks entry to first ret that everything still points to valid data. */ + Result ValidatePayloadFn(const void *fn) const { + constexpr u32 MaxInstructions = 512; + constexpr u32 RetInsn = 0xD65F03C0u; + + const uintptr_t lo = m_payload; + const uintptr_t hi = m_payload + m_payload_sz; + + const u32 *insns = reinterpret_cast(fn); + const uintptr_t base = m_payload + (reinterpret_cast(fn) - reinterpret_cast(__start_hoc_hookpayload)); + + auto check = [&](uintptr_t target) -> Result { + if (target < lo || target >= hi) { + R_THROW(ldr::ResultHookPayloadEscapes()); + } + R_SUCCEED(); + }; + + for (u32 i = 0; i < MaxInstructions; ++i) { + const u32 insn = insns[i]; + const uintptr_t site = base + i * sizeof(u32); + + if (insn == RetInsn) { + R_SUCCEED(); + } + + const u32 top6 = insn & 0xFC000000u; + if (top6 == 0x14000000u || top6 == 0x94000000u) { /* b / bl */ + const uintptr_t target = site + SignExtend(static_cast(insn & 0x03FFFFFFu) << 2, 28); + R_TRY(check(target)); + } else if ((insn & 0xFF000010u) == 0x54000000u || /* b.cond */ + (insn & 0x7E000000u) == 0x34000000u || /* cbz / cbnz */ + (insn & 0x3B000000u) == 0x18000000u) { /* ldr literal */ + const uintptr_t target = site + SignExtend(static_cast((insn >> 5) & 0x7FFFFu) << 2, 21); + R_TRY(check(target)); + } else if ((insn & 0x7E000000u) == 0x36000000u) { /* tbz / tbnz */ + const uintptr_t target = site + SignExtend(static_cast((insn >> 5) & 0x3FFFu) << 2, 16); + R_TRY(check(target)); + } else if ((insn & 0x9F000000u) == 0x10000000u) { /* adr */ + const u64 imm = (static_cast((insn >> 5) & 0x7FFFFu) << 2) | ((insn >> 29) & 0x3u); + const uintptr_t target = site + SignExtend(imm, 21); + R_TRY(check(target)); + } else if ((insn & 0x9F000000u) == 0x90000000u) { /* adrp */ + /* ADRP is page-relative and the cave is at an arbitrary offset, any adrp would point to potential garbage. */ + R_THROW(ldr::ResultHookPayloadEscapes()); + } + } + + /* No return found. */ + R_THROW(ldr::ResultHookPayloadEscapes()); + } + private: + u32 *AllocCode(size_t words) { + const size_t bytes = words * sizeof(u32); + if (!this->IsEnabled() || m_used + bytes > m_cave_size) { + return nullptr; + } + + u32 *p = reinterpret_cast(m_cave + m_used); + m_used += bytes; + return p; + } + }; + + inline HookContext &Hooks() { + static HookContext s_context; + return s_context; + } + +} + +/* Hook custom impl. Original becomes unreachable. */ +/* Starts at function entry. Preserves arguments. */ +#define INSTALL_IMPL_HOOK(site, fn) \ + (::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast(&(fn)))) + +/* Maintains original function. */ +/* Starts at function entry. Preserves arguments. */ +#define INSTALL_IMPL_HOOK_ORIG(site, fn, out_orig) \ + (::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast(&(fn)), (out_orig))) + +/* Takes the same arguments as the hooked function, does not replace it and cannot change what it does */ +/* but it can be placed anywhere, not just at a function entry. */ +#define INSTALL_INTERC_HOOK(site, fn) \ + (::ams::ldr::hoc::pcv::Hooks().InstallIntercept((site), reinterpret_cast(&(fn))))