ldr: add hooking capabilities
This commit is contained in:
@@ -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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(nso_header->text_size);
|
||||
const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize);
|
||||
const size_t ro_start = static_cast<size_t>(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<u32>(hoc::HocPcvScratchSize);
|
||||
g_nso_headers[ctx.main_nso_idx].bss_size += static_cast<u32>(hoc::pcv::HocPcvScratchSize);
|
||||
}
|
||||
|
||||
/* Get CreateProcessParameter. */
|
||||
|
||||
@@ -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<typename Pointer>
|
||||
struct PatcherEntry {
|
||||
using patternFn = bool(*)(Pointer *ptr);
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace ams::ldr::hoc::pcv {
|
||||
} else {
|
||||
marikoCpuDvfsMaxFreq = static_cast<u32>(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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
364
Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
Normal file
364
Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<s64>(value << shift) >> shift;
|
||||
}
|
||||
|
||||
inline u32 EncodeRelBranch(u32 opc, uintptr_t site_va, uintptr_t target_va) {
|
||||
const s64 delta = static_cast<s64>(target_va) - static_cast<s64>(site_va);
|
||||
AMS_ABORT_UNLESS((delta & 0x3) == 0);
|
||||
AMS_ABORT_UNLESS(delta >= -0x08000000 && delta <= 0x07FFFFFC);
|
||||
return opc | (static_cast<u32>(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<u32>((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<u64>(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<uintptr_t>(loader_ptr) - m_map_base);
|
||||
}
|
||||
|
||||
uintptr_t CaveVa() const { return ToVa(reinterpret_cast<const void *>(m_cave)); }
|
||||
|
||||
Result CopyPayload() {
|
||||
R_TRY(this->CheckEnabled());
|
||||
|
||||
const size_t size = static_cast<size_t>(__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<void *>(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<uintptr_t>(loader_sym) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
|
||||
return this->ToVa(reinterpret_cast<void *>(m_payload + offset));
|
||||
}
|
||||
|
||||
/* Loader-side, writable pointer to a payload variable's copy in the cave. */
|
||||
template<typename T>
|
||||
T *PayloadCopyOf(T &loader_sym) const {
|
||||
const uintptr_t offset = reinterpret_cast<uintptr_t>(std::addressof(loader_sym)) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
|
||||
return reinterpret_cast<T *>(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<typename T>
|
||||
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<T *>(m_data + m_data_used);
|
||||
m_data_used += size;
|
||||
|
||||
std::memset(p, 0, sizeof(T));
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *BindData(T *&loader_sym) {
|
||||
T *block = this->DataAlloc<T>();
|
||||
if (block != nullptr) {
|
||||
*this->PayloadCopyOf(loader_sym) = reinterpret_cast<T *>(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<s32>(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<s32>(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<const u32 *>(fn);
|
||||
const uintptr_t base = m_payload + (reinterpret_cast<uintptr_t>(fn) - reinterpret_cast<uintptr_t>(__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<u64>(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<u64>((insn >> 5) & 0x7FFFFu) << 2, 21);
|
||||
R_TRY(check(target));
|
||||
} else if ((insn & 0x7E000000u) == 0x36000000u) { /* tbz / tbnz */
|
||||
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x3FFFu) << 2, 16);
|
||||
R_TRY(check(target));
|
||||
} else if ((insn & 0x9F000000u) == 0x10000000u) { /* adr */
|
||||
const u64 imm = (static_cast<u64>((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<u32 *>(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<const void *>(&(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<const void *>(&(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<const void *>(&(fn))))
|
||||
Reference in New Issue
Block a user