diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index dfb54d49..0f9894b7 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -232,6 +232,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/loader/nca.cpp ${source_DIR}/skyline/loader/xci.cpp ${source_DIR}/skyline/loader/nsp.cpp + ${source_DIR}/skyline/hle/symbol_hooks.cpp ${source_DIR}/skyline/vfs/partition_filesystem.cpp ${source_DIR}/skyline/vfs/ctr_encrypted_backing.cpp ${source_DIR}/skyline/vfs/rom_filesystem.cpp diff --git a/app/src/main/cpp/skyline/common/trace.h b/app/src/main/cpp/skyline/common/trace.h index 78823c44..07d317b4 100644 --- a/app/src/main/cpp/skyline/common/trace.h +++ b/app/src/main/cpp/skyline/common/trace.h @@ -11,6 +11,7 @@ PERFETTO_DEFINE_CATEGORIES( perfetto::Category("scheduler").SetDescription("Events from the HLE scheduler"), perfetto::Category("kernel").SetDescription("Events from parts of the HLE kernel"), + perfetto::Category("hook").SetDescription("Events from HLE hooks"), perfetto::Category("guest").SetDescription("Events relating to guest code"), perfetto::Category("host").SetDescription("Events relating to host code"), perfetto::Category("gpu").SetDescription("Events from the emulated GPU"), diff --git a/app/src/main/cpp/skyline/hle/symbol_hook_table.h b/app/src/main/cpp/skyline/hle/symbol_hook_table.h new file mode 100644 index 00000000..ef5b2859 --- /dev/null +++ b/app/src/main/cpp/skyline/hle/symbol_hook_table.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "symbol_hooks.h" + +namespace skyline::hle { + struct HookTableEntry { + std::string_view name; //!< The name of the symbol + HookType hook; //!< The hook that the symbol should include + + HookTableEntry(std::string_view name, HookType hook) : name{name}, hook{std::move(hook)} {} + }; + + static std::array HookedSymbols{}; +} diff --git a/app/src/main/cpp/skyline/hle/symbol_hooks.cpp b/app/src/main/cpp/skyline/hle/symbol_hooks.cpp new file mode 100644 index 00000000..9dce0868 --- /dev/null +++ b/app/src/main/cpp/skyline/hle/symbol_hooks.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include "symbol_hooks.h" + +namespace skyline::hle { + std::string Demangle(const std::string_view mangledName) { + int status{}; + size_t length{}; + std::unique_ptr demangled{abi::__cxa_demangle(mangledName.data(), nullptr, &length, &status), std::free}; + return std::string{status == 0 ? std::string_view{demangled.get()} : mangledName}; + } + + HookedSymbol::HookedSymbol(std::string pName, HookType hook) : name{std::move(pName)}, prettyName{Demangle(name)}, hook{std::move(hook)} {} +} diff --git a/app/src/main/cpp/skyline/hle/symbol_hooks.h b/app/src/main/cpp/skyline/hle/symbol_hooks.h new file mode 100644 index 00000000..2b09aa2a --- /dev/null +++ b/app/src/main/cpp/skyline/hle/symbol_hooks.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include + +namespace skyline::hle { + struct HookedSymbol; + + using HookFunction = std::function; + + using OverrideHook = HookFunction; //!< A function that overrides the execution of a hooked function + + struct EntryExitHook { + HookFunction entry; //!< The hook to be called when the function is entered + HookFunction exit; //!< The hook to be called when the function is exited + }; + + using HookType = std::variant; + + struct HookedSymbol { + std::string name; //!< The name of the symbol + std::string prettyName; //!< The demangled name of the symbol + HookType hook; //!< The hook that the symbol should include + + HookedSymbol(std::string name, HookType hook); + }; +} diff --git a/app/src/main/cpp/skyline/loader/executable.h b/app/src/main/cpp/skyline/loader/executable.h index 825d8cbf..41a63bf2 100644 --- a/app/src/main/cpp/skyline/loader/executable.h +++ b/app/src/main/cpp/skyline/loader/executable.h @@ -6,6 +6,27 @@ #include namespace skyline::loader { + /** + * @brief The MOD header embeds metadata about an executable into it + * @url https://switchbrew.org/wiki/NSO#MOD + */ + struct MOD { + u32 reserved; + u32 magicOffset; + u32 magic; + u32 dynamicOffset; + u32 bssStart; + u32 bssEnd; + u32 ehFrameHdrStart; + u32 ehFrameHdrEnd; + u32 moduleOffset; + + bool IsValid() { + constexpr u32 MODMagic{util::MakeMagic("MOD0")}; + return magic == MODMagic; + } + }; + /** * @brief The contents of an executable binary abstracted away from the derivatives of Loader */ diff --git a/app/src/main/cpp/skyline/loader/loader.cpp b/app/src/main/cpp/skyline/loader/loader.cpp index 3dd44e4b..bdcec68a 100644 --- a/app/src/main/cpp/skyline/loader/loader.cpp +++ b/app/src/main/cpp/skyline/loader/loader.cpp @@ -7,55 +7,111 @@ #include #include #include +#include #include "loader.h" namespace skyline::loader { - Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr &process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name) { + Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr &process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name, bool dynamicallyLinked) { u8 *base{reinterpret_cast(process->memory.base.data() + offset)}; - u64 textSize{executable.text.contents.size()}; - u64 roSize{executable.ro.contents.size()}; - u64 dataSize{executable.data.contents.size() + executable.bssSize}; + size_t textSize{executable.text.contents.size()}; + size_t roSize{executable.ro.contents.size()}; + size_t dataSize{executable.data.contents.size() + executable.bssSize}; if (!util::IsPageAligned(textSize) || !util::IsPageAligned(roSize) || !util::IsPageAligned(dataSize)) - throw exception("LoadProcessData: Sections are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", textSize, roSize, dataSize); + throw exception("Sections are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", textSize, roSize, dataSize); + + if (executable.text.offset != 0) + throw exception("Executable's .text offset is not 0: 0x{:X}", executable.text.offset); if (!util::IsPageAligned(executable.text.offset) || !util::IsPageAligned(executable.ro.offset) || !util::IsPageAligned(executable.data.offset)) - throw exception("LoadProcessData: Section offsets are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", executable.text.offset, executable.ro.offset, executable.data.offset); + throw exception("Section offsets are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", executable.text.offset, executable.ro.offset, executable.data.offset); auto patch{state.nce->GetPatchData(executable.text.contents)}; - auto size{patch.size + textSize + roSize + dataSize}; - process->NewHandle(span{base, patch.size}, memory::Permission{false, false, false}, memory::states::Reserved); // --- - Logger::Debug("Successfully mapped section .patch @ 0x{:X}, Size = 0x{:X}", base, patch.size); + span dynsym{reinterpret_cast(executable.ro.contents.data() + executable.dynsym.offset), executable.dynsym.size / sizeof(Elf64_Sym)}; + span dynstr{reinterpret_cast(executable.ro.contents.data() + executable.dynstr.offset), executable.dynstr.size}; + std::vector executableSymbols; + size_t hookSize{}; + if (dynamicallyLinked) { + for (auto &symbol : dynsym) { + if (symbol.st_name == 0 || symbol.st_value == 0) + continue; - process->NewHandle(span{base + patch.size + executable.text.offset, textSize}, memory::Permission{true, false, true}, memory::states::CodeStatic); // R-X - Logger::Debug("Successfully mapped section .text @ 0x{:X}, Size = 0x{:X}", base + patch.size + executable.text.offset, textSize); + if (ELF64_ST_TYPE(symbol.st_info) != STT_FUNC || ELF64_ST_BIND(symbol.st_info) != STB_GLOBAL || symbol.st_shndx == SHN_UNDEF) + continue; - process->NewHandle(span{base + patch.size + executable.ro.offset, roSize}, memory::Permission{true, false, false}, memory::states::CodeStatic); // R-- - Logger::Debug("Successfully mapped section .rodata @ 0x{:X}, Size = 0x{:X}", base + patch.size + executable.ro.offset, roSize); + std::string_view symbolName{dynstr.data() + symbol.st_name}; - process->NewHandle(span{base + patch.size + executable.data.offset, dataSize}, memory::Permission{true, true, false}, memory::states::CodeMutable); // RW- - Logger::Debug("Successfully mapped section .data + .bss @ 0x{:X}, Size = 0x{:X}", base + patch.size + executable.data.offset, dataSize); + auto item{std::find_if(hle::HookedSymbols.begin(), hle::HookedSymbols.end(), [&symbolName](const auto &item) { + return item.name == symbolName; + })}; + if (item != hle::HookedSymbols.end()) { + executableSymbols.emplace_back(std::string{symbolName}, item->hook, &symbol.st_value); + continue; + } - state.nce->PatchCode(executable.text.contents, reinterpret_cast(base), patch.size, patch.offsets); - std::memcpy(base + patch.size + executable.text.offset, executable.text.contents.data(), textSize); - std::memcpy(base + patch.size + executable.ro.offset, executable.ro.contents.data(), roSize); - std::memcpy(base + patch.size + executable.data.offset, executable.data.contents.data(), dataSize - executable.bssSize); + #ifdef PRINT_HOOK_ALL + if (symbolName == "memcpy" || symbolName == "memcmp" || symbolName == "memset" || symbolName == "strcmp" || symbolName == "strlen") + // If symbol is from libc (such as memcpy, strcmp, strlen, etc), we don't need to hook it + continue; - auto rodataOffset{base + patch.size + executable.ro.offset}; - ExecutableSymbolicInfo symbolicInfo{ - .patchStart = base, - .programStart = base + patch.size, - .programEnd = base + size, - .name = name, - .patchName = name + ".patch", - .symbols = span(reinterpret_cast(rodataOffset + executable.dynsym.offset), executable.dynsym.size / sizeof(Elf64_Sym)), - .symbolStrings = span(reinterpret_cast(rodataOffset + executable.dynstr.offset), executable.dynstr.size), - }; - executables.insert(std::upper_bound(executables.begin(), executables.end(), base, [](void *ptr, const ExecutableSymbolicInfo &it) { return ptr < it.patchStart; }), symbolicInfo); + executableSymbols.emplace_back(std::string{symbolName}, hle::EntryExitHook{ + .entry = [](const DeviceState &, const hle::HookedSymbol &symbol) { + Logger::Error("Entering \"{}\" ({})", symbol.prettyName, symbol.name); + }, + .exit = [](const DeviceState &, const hle::HookedSymbol &symbol) { + Logger::Error("Exiting \"{}\"", symbol.prettyName); + }, + }, &symbol.st_value); + #endif + } - return {base, size, base + patch.size}; + hookSize = util::AlignUp(state.nce->GetHookSectionSize(executableSymbols), PAGE_SIZE); + } + + process->NewHandle(span{base, patch.size + hookSize}, memory::Permission{false, false, false}, memory::states::Reserved); // --- + Logger::Error("Successfully mapped section .patch @ 0x{:X}, Size = 0x{:X}", base, patch.size); + if (hookSize > 0) + Logger::Error("Successfully mapped section .hook @ 0x{:X}, Size = 0x{:X}", base + patch.size, hookSize); + + u8 *executableBase{base + patch.size + hookSize}; + process->NewHandle(span{executableBase + executable.text.offset, textSize}, memory::Permission{true, false, true}, memory::states::CodeStatic); // R-X + Logger::Error("Successfully mapped section .text @ 0x{:X}, Size = 0x{:X}", executableBase, textSize); + + process->NewHandle(span{executableBase + executable.ro.offset, roSize}, memory::Permission{true, false, false}, memory::states::CodeStatic); // R-- + Logger::Error("Successfully mapped section .rodata @ 0x{:X}, Size = 0x{:X}", executableBase + executable.ro.offset, roSize); + + process->NewHandle(span{executableBase + executable.data.offset, dataSize}, memory::Permission{true, true, false}, memory::states::CodeMutable); // RW- + Logger::Error("Successfully mapped section .data + .bss @ 0x{:X}, Size = 0x{:X}", executableBase + executable.data.offset, dataSize); + + size_t size{patch.size + hookSize + textSize + roSize + dataSize}; + { + // Note: We need to copy out the symbols here as it'll be overwritten by any hooks + ExecutableSymbolicInfo symbolicInfo{ + .patchStart = base, + .hookStart = base + patch.size, + .programStart = executableBase, + .programEnd = base + size, + .name = name, + .patchName = name + ".patch", + .hookName = name + ".hook", + .symbols = {dynsym.begin(), dynsym.end()}, + .symbolStrings = {dynstr.begin(), dynstr.end()}, + }; + executables.insert(std::upper_bound(executables.begin(), executables.end(), base, [](void *ptr, const ExecutableSymbolicInfo &it) { return ptr < it.patchStart; }), std::move(symbolicInfo)); + } + + state.nce->PatchCode(executable.text.contents, reinterpret_cast(base), patch.size, patch.offsets, hookSize); + if (hookSize) + state.nce->WriteHookSection(executableSymbols, span{base + patch.size, hookSize}.cast()); + + std::memcpy(executableBase, executable.text.contents.data(), executable.text.contents.size()); + std::memcpy(executableBase + executable.ro.offset, executable.ro.contents.data(), roSize); + std::memcpy(executableBase + executable.data.offset, executable.data.contents.data(), dataSize - executable.bssSize); + + Logger::EmulationContext.Flush(); + return {base, size, executableBase + executable.text.offset}; } Loader::SymbolInfo Loader::ResolveSymbol(void *ptr) { @@ -69,6 +125,8 @@ namespace skyline::loader { } else { return {.executableName = executable->name}; } + } else if (ptr >= executable->hookStart) { + return {.executableName = executable->hookName}; } else { return {.executableName = executable->patchName}; } diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index ec4bec38..1ef1bea9 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -54,12 +54,14 @@ namespace skyline::loader { */ struct ExecutableSymbolicInfo { void *patchStart; //!< A pointer to the start of this executable's patch section + void *hookStart; //!< A pointer to the start of this executable's hook section void *programStart; //!< A pointer to the start of the executable void *programEnd; //!< A pointer to the end of the executable std::string name; //!< The name of the executable std::string patchName; //!< The name of the patch section - span symbols; //!< A span over the .dynsym section - span symbolStrings; //!< A span over the .dynstr section + std::string hookName; //!< The name of the hook section + std::vector symbols; //!< A span over the .dynsym section + std::vector symbolStrings; //!< A span over the .dynstr section }; std::vector executables; @@ -80,7 +82,7 @@ namespace skyline::loader { * @param name An optional name for the executable, used for symbol resolution * @return An ExecutableLoadInfo struct containing the load base and size */ - ExecutableLoadInfo LoadExecutable(const std::shared_ptr &process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string &name = {}); + ExecutableLoadInfo LoadExecutable(const std::shared_ptr &process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string &name = {}, bool dynamicallyLinked = false); std::optional nacp; std::shared_ptr romFs; diff --git a/app/src/main/cpp/skyline/loader/nca.cpp b/app/src/main/cpp/skyline/loader/nca.cpp index 0e56708f..4f0a1d52 100644 --- a/app/src/main/cpp/skyline/loader/nca.cpp +++ b/app/src/main/cpp/skyline/loader/nca.cpp @@ -36,7 +36,7 @@ namespace skyline::loader { else continue; - loadInfo = NsoLoader::LoadNso(loader, nsoFile, process, state, offset, nso + std::string(".nso")); + loadInfo = NsoLoader::LoadNso(loader, nsoFile, process, state, offset, nso + std::string(".nso"), true); Logger::Info("Loaded '{}.nso' at 0x{:X} (.text @ 0x{:X})", nso, base + offset, loadInfo.entry); offset += loadInfo.size; } diff --git a/app/src/main/cpp/skyline/loader/nso.cpp b/app/src/main/cpp/skyline/loader/nso.cpp index 5d603662..ffd726a6 100644 --- a/app/src/main/cpp/skyline/loader/nso.cpp +++ b/app/src/main/cpp/skyline/loader/nso.cpp @@ -29,7 +29,7 @@ namespace skyline::loader { return outputBuffer; } - Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr &backing, const std::shared_ptr &process, const DeviceState &state, size_t offset, const std::string &name) { + Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr &backing, const std::shared_ptr &process, const DeviceState &state, size_t offset, const std::string &name, bool dynamicallyLinked) { auto header{backing->Read()}; if (header.magic != util::MakeMagic("NSO0")) @@ -56,7 +56,7 @@ namespace skyline::loader { executable.dynstr = {header.dynstr.offset, header.dynstr.size}; } - return loader->LoadExecutable(process, state, executable, offset, name); + return loader->LoadExecutable(process, state, executable, offset, name, dynamicallyLinked); } void *NsoLoader::LoadProcessData(const std::shared_ptr &process, const DeviceState &state) { diff --git a/app/src/main/cpp/skyline/loader/nso.h b/app/src/main/cpp/skyline/loader/nso.h index 8ba29dce..93738c2b 100644 --- a/app/src/main/cpp/skyline/loader/nso.h +++ b/app/src/main/cpp/skyline/loader/nso.h @@ -87,7 +87,7 @@ namespace skyline::loader { * @param name An optional name for the NSO, used for symbol resolution * @return An ExecutableLoadInfo struct containing the load base and size */ - static ExecutableLoadInfo LoadNso(Loader *loader, const std::shared_ptr &backing, const std::shared_ptr &process, const DeviceState &state, size_t offset = 0, const std::string &name = {}); + static ExecutableLoadInfo LoadNso(Loader *loader, const std::shared_ptr &backing, const std::shared_ptr &process, const DeviceState &state, size_t offset = 0, const std::string &name = {}, bool dynamicallyLinked = false); void *LoadProcessData(const std::shared_ptr &process, const DeviceState &state) override; }; diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 64b02d84..638f36a9 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -92,6 +92,67 @@ namespace skyline::nce { TRACE_EVENT_BEGIN("guest", "Guest"); } + void NCE::HookHandler(HookId hookId, ThreadContext *ctx) { + const auto &state{*ctx->state}; + auto hookedSymbol{state.nce->hookedSymbols[hookId.index]}; + try { + std::visit(VariantVisitor{ + [&](const hle::OverrideHook &hook) { + TRACE_EVENT("hook", nullptr, [&](perfetto::EventContext ctx) { + ctx.event()->set_name(hookedSymbol.prettyName); + }); + hook(state, hookedSymbol); + }, + [&](const hle::EntryExitHook &hook) { + if (!hookId.isExit) { + TRACE_EVENT_BEGIN("hook", nullptr, [&](perfetto::EventContext ctx) { + ctx.event()->set_name(hookedSymbol.prettyName); + }); + hook.entry(state, hookedSymbol); + } else { + hook.exit(state, hookedSymbol); + TRACE_EVENT_END("hook"); + } + }, + }, hookedSymbol.hook); + + while (kernel::Scheduler::YieldPending) [[unlikely]] { + state.scheduler->Rotate(false); + kernel::Scheduler::YieldPending = false; + state.scheduler->WaitSchedule(); + } + } catch (const signal::SignalException &e) { + if (e.signal != SIGINT) { + Logger::ErrorNoPrefix("{} (Hook: {})\nStack Trace:{}", e.what(), hookedSymbol.prettyName, state.loader->GetStackTrace(e.frames)); + Logger::EmulationContext.Flush(); + + if (state.thread->id) { + signal::BlockSignal({SIGINT}); + state.process->Kill(false); + } + } else { + Logger::EmulationContext.Flush(); + } + + abi::__cxa_end_catch(); + std::longjmp(state.thread->originalCtx, true); + } catch (const exception &e) { + Logger::ErrorNoPrefix("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames)); + Logger::EmulationContext.Flush(); + + if (state.thread->id) { + signal::BlockSignal({SIGINT}); + state.process->Kill(false); + } + + abi::__cxa_end_catch(); + std::longjmp(state.thread->originalCtx, true); + } catch (const std::exception &e) { + Logger::ErrorNoPrefix("{} (Hook: {})\nStack Trace:{}", e.what(), hookedSymbol.prettyName, state.loader->GetStackTrace()); + Logger::EmulationContext.Flush(); + } + } + void NCE::SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls) { if (*tls) { // If TLS was restored then this occurred in guest code auto &mctx{ctx->uc_mcontext}; @@ -195,7 +256,47 @@ namespace skyline::nce { staticNce = nullptr; } - constexpr u8 MainSvcTrampolineSize{17}; // Size of the main SVC trampoline function in u32 units + constexpr size_t TrampolineSize{17}; // Size of the main SVC trampoline function in u32 units + + /** + * @brief Writes a trampoline to the given target address that saves the current context and calls the given function + */ + u32 *WriteTrampoline(u32 *code, u64 target) { + /* Hook Trampoline */ + /* Store LR in 16B of pre-allocated stack */ + *code++ = 0xF90007FE; // STR LR, [SP, #8] + + /* Replace Skyline TLS with host TLS */ + *code++ = 0xD53BD041; // MRS X1, TPIDR_EL0 + *code++ = 0xF9415022; // LDR X2, [X1, #0x2A0] (ThreadContext::hostTpidrEl0) + *code++ = 0xD51BD042; // MSR TPIDR_EL0, X2 + + /* Replace guest stack with host stack */ + *code++ = 0x910003E2; // MOV X2, SP + *code++ = 0xF9415423; // LDR X3, [X1, #0x2A8] (ThreadContext::hostSp) + *code++ = 0x9100007F; // MOV SP, X3 + + /* Store Skyline TLS + guest SP on stack */ + *code++ = 0xA9BF0BE1; // STP X1, X2, [SP, #-16]! + + /* Jump to SvcHandler */ + for (const auto &mov : instructions::MoveRegister(registers::X2, target)) + if (mov) + *code++ = mov; + *code++ = 0xD63F0040; // BLR X2 + + /* Restore Skyline TLS + guest SP */ + *code++ = 0xA8C10BE1; // LDP X1, X2, [SP], #16 + *code++ = 0xD51BD041; // MSR TPIDR_EL0, X1 + *code++ = 0x9100005F; // MOV SP, X2 + + /* Restore LR and Return */ + *code++ = 0xF94007FE; // LDR LR, [SP, #8] + *code++ = 0xD65F03C0; // RET + + return code; + } + constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS constexpr u32 TpidrroEl0{0x5E83}; // ID of TPIDRRO_EL0 in MRS constexpr u32 CntfrqEl0{0x5F00}; // ID of CNTFRQ_EL0 in MRS @@ -204,7 +305,7 @@ namespace skyline::nce { constexpr u32 TegraX1Freq{19200000}; // The clock frequency of the Tegra X1 (19.2 MHz) NCE::PatchData NCE::GetPatchData(const std::vector &text) { - size_t size{guest::SaveCtxSize + guest::LoadCtxSize + MainSvcTrampolineSize}; + size_t size{guest::SaveCtxSize + guest::LoadCtxSize + TrampolineSize}; std::vector offsets; u64 frequency; @@ -246,46 +347,14 @@ namespace skyline::nce { return {util::AlignUp(size * sizeof(u32), constant::PageSize), offsets}; } - void NCE::PatchCode(std::vector &text, u32 *patch, size_t patchSize, const std::vector &offsets) { + void NCE::PatchCode(std::vector &text, u32 *patch, size_t patchSize, const std::vector &offsets, size_t textOffset) { u32 *start{patch}; u32 *end{patch + (patchSize / sizeof(u32))}; std::memcpy(patch, reinterpret_cast(&guest::SaveCtx), guest::SaveCtxSize * sizeof(u32)); patch += guest::SaveCtxSize; - { - /* Main SVC Trampoline */ - /* Store LR in 16B of pre-allocated stack */ - *patch++ = 0xF90007FE; // STR LR, [SP, #8] - - /* Replace Skyline TLS with host TLS */ - *patch++ = 0xD53BD041; // MRS X1, TPIDR_EL0 - *patch++ = 0xF9415022; // LDR X2, [X1, #0x2A0] (ThreadContext::hostTpidrEl0) - *patch++ = 0xD51BD042; // MSR TPIDR_EL0, X2 - - /* Replace guest stack with host stack */ - *patch++ = 0x910003E2; // MOV X2, SP - *patch++ = 0xF9415423; // LDR X3, [X1, #0x2A8] (ThreadContext::hostSp) - *patch++ = 0x9100007F; // MOV SP, X3 - - /* Store Skyline TLS + guest SP on stack */ - *patch++ = 0xA9BF0BE1; // STP X1, X2, [SP, #-16]! - - /* Jump to SvcHandler */ - for (const auto &mov : instructions::MoveRegister(registers::X2, reinterpret_cast(&NCE::SvcHandler))) - if (mov) - *patch++ = mov; - *patch++ = 0xD63F0040; // BLR X2 - - /* Restore Skyline TLS + guest SP */ - *patch++ = 0xA8C10BE1; // LDP X1, X2, [SP], #16 - *patch++ = 0xD51BD041; // MSR TPIDR_EL0, X1 - *patch++ = 0x9100005F; // MOV SP, X2 - - /* Restore LR and Return */ - *patch++ = 0xF94007FE; // LDR LR, [SP, #8] - *patch++ = 0xD65F03C0; // RET - } + patch = WriteTrampoline(patch, reinterpret_cast(&NCE::SvcHandler)); std::memcpy(patch, reinterpret_cast(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32)); patch += guest::LoadCtxSize; @@ -299,7 +368,7 @@ namespace skyline::nce { auto svc{*reinterpret_cast(instruction)}; auto mrs{*reinterpret_cast(instruction)}; auto msr{*reinterpret_cast(instruction)}; - auto endOffset{[&] { return static_cast(end - patch); }}; + auto endOffset{[&] { return static_cast(end - patch) + (textOffset / sizeof(u32)); }}; auto startOffset{[&] { return static_cast(start - patch); }}; if (svc.Verify()) { @@ -318,7 +387,7 @@ namespace skyline::nce { patch++; /* Restore Context and Return */ - *patch = instructions::BL(static_cast(startOffset() + guest::SaveCtxSize + MainSvcTrampolineSize)).raw; + *patch = instructions::BL(static_cast(startOffset() + guest::SaveCtxSize + TrampolineSize)).raw; patch++; *patch++ = 0xF84107FE; // LDR LR, [SP], #16 *patch = instructions::B(static_cast(endOffset() + offset + 1)).raw; @@ -406,6 +475,95 @@ namespace skyline::nce { } } + size_t NCE::GetHookSectionSize(span entries) { + size_t size{guest::SaveCtxSize + guest::LoadCtxSize + TrampolineSize}; + for (const auto &entry : entries) { + constexpr size_t EmitTrampolineSize{10}; + if (std::holds_alternative(entry.hook)) + size += EmitTrampolineSize + 1; + else if (std::holds_alternative(entry.hook)) + size += 4 + EmitTrampolineSize + 1 + EmitTrampolineSize + 4 + 1; + } + return size * sizeof(u32); + } + + void NCE::WriteHookSection(span entries, span hookSection) { + u32 *start{hookSection.data()}; + u32 *end{hookSection.end().base()}; + u32 *hook{start}; + + std::memcpy(hook, reinterpret_cast(&guest::SaveCtx), guest::SaveCtxSize * sizeof(u32)); + hook += guest::SaveCtxSize; + + hook = WriteTrampoline(hook, reinterpret_cast(&NCE::HookHandler)); + + std::memcpy(hook, reinterpret_cast(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32)); + hook += guest::LoadCtxSize; + + u64 hookIndex{static_cast(hookedSymbols.size())}; + hookedSymbols.reserve(entries.size()); + for (const auto &entry : entries) { + auto startOffset{[&] { return static_cast(start - hook); }}; + auto endOffset{[&] { return static_cast(end - hook); }}; + auto emitTrampoline{[&](HookId hookId) { + /* Save Context */ + *hook++ = 0xF81F0FFE; // STR LR, [SP, #-16]! + *hook = instructions::BL(static_cast(startOffset())).raw; // BL SaveCtx + hook++; + + /* Jump to entry trampoline */ + for (const auto &mov : instructions::MoveRegister(registers::X0, hookId.raw)) + if (mov) + *hook++ = mov; + *hook = instructions::BL(static_cast(startOffset() + guest::SaveCtxSize)).raw; // BL HookTrampoline + hook++; + + /* Restore Context */ + *hook = instructions::BL(static_cast(startOffset() + guest::SaveCtxSize + TrampolineSize)).raw; // BL LoadCtx + hook++; + *hook++ = 0xF84107FE; // LDR LR, [SP], #16 + }}; + + Elf64_Addr originalOffset{*entry.offset}; + *entry.offset = -(endOffset() * sizeof(u32)); + + if (std::holds_alternative(entry.hook)) { + /* Override Hook */ + emitTrampoline(HookId{hookIndex, false}); + } else if (std::holds_alternative(entry.hook)) { + /* TLS LR Store */ + *hook++ = 0xA9BF07E0; // STP X0, X1, [SP, #-16]! + *hook++ = 0xD53BD040; // MRS X0, TPIDR_EL0 + *hook++ = 0xF9415401; // LDR X1, [X0, #0x2A8] (ThreadContext::hostSp) + *hook++ = 0xF81F0C3E; // STR LR, [X1, #-16]! + *hook++ = 0xF9015401; // STR X1, [X0, #0x2A8] (ThreadContext::hostSp) + *hook++ = 0xA8C107E0; // LDP X0, X1, [SP], #16 + + /* Entry Hook */ + emitTrampoline(HookId{hookIndex, false}); + + /* Function Proxy */ + *hook++ = instructions::BL(static_cast(endOffset() + (originalOffset / sizeof(u32)))).raw; + + /* Exit Hook */ + emitTrampoline(HookId{hookIndex, true}); + + /* TLS LR Load */ + *hook++ = 0xA9BF07E0; // STP X0, X1, [SP, #-16]! + *hook++ = 0xD53BD040; // MRS X0, TPIDR_EL0 + *hook++ = 0xF9415401; // LDR X1, [X0, #0x2A8] (ThreadContext::hostSp) + *hook++ = 0xF841043E; // LDR LR, [X1], #16 + *hook++ = 0xF9015401; // STR X1, [X0, #0x2A8] (ThreadContext::hostSp) + *hook++ = 0xA8C107E0; // LDP X0, X1, [SP], #16 + } + + *hook++ = 0xD65F03C0; // RET + + hookedSymbols.emplace_back(entry); + hookIndex++; + } + } + NCE::CallbackEntry::CallbackEntry(TrapProtection protection, LockCallback lockCallback, TrapCallback readCallback, TrapCallback writeCallback) : protection{protection}, lockCallback{std::move(lockCallback)}, readCallback{std::move(readCallback)}, writeCallback{std::move(writeCallback)} {} void NCE::ReprotectIntervals(const std::vector &intervals, TrapProtection protection) { diff --git a/app/src/main/cpp/skyline/nce.h b/app/src/main/cpp/skyline/nce.h index 3429b9d0..4534ed98 100644 --- a/app/src/main/cpp/skyline/nce.h +++ b/app/src/main/cpp/skyline/nce.h @@ -4,7 +4,9 @@ #pragma once #include +#include #include "common.h" +#include "hle/symbol_hooks.h" #include "common/interval_map.h" namespace skyline::nce { @@ -15,6 +17,8 @@ namespace skyline::nce { private: const DeviceState &state; + std::vector hookedSymbols; //!< The list of symbols that are hooked, these have a specific ordering that is hardcoded into the hooked functions + /** * @brief The level of protection that is required for a callback entry */ @@ -48,6 +52,23 @@ namespace skyline::nce { static void SvcHandler(u16 svcId, ThreadContext *ctx); + /** + * @brief A parameter packed into a 64-bit register denoting the hook which is being called + */ + struct HookId { + union { + struct { + u64 index : 63; + u64 isExit : 1; + }; + u64 raw; + }; + + constexpr HookId(u64 index, bool isExit) : index{index}, isExit{isExit} {} + }; + + static void HookHandler(HookId hookId, ThreadContext *ctx); + public: /** * @brief An exception which causes the throwing thread to exit alongside all threads optionally @@ -89,8 +110,19 @@ namespace skyline::nce { /** * @brief Writes the .patch section and mutates the code accordingly * @param patch A pointer to the .patch section which should be exactly patchSize in size and located before the .text section + * @param textOffset The offset of the .text section, this must be page-aligned */ - static void PatchCode(std::vector &text, u32 *patch, size_t patchSize, const std::vector &offsets); + static void PatchCode(std::vector &text, u32 *patch, size_t patchSize, const std::vector &offsets, size_t textOffset = 0); + + struct HookedSymbolEntry : hle::HookedSymbol { + Elf64_Addr* offset{}; //!< A pointer to the hooked function's offset (st_value) in the ELF's dynsym, this is set by the loader and is used to resolve/update the address of the function + + HookedSymbolEntry(std::string name, const hle::HookType &hook, Elf64_Addr* offset) : HookedSymbol{std::move(name), hook}, offset{offset} {} + }; + + static size_t GetHookSectionSize(span entries); + + void WriteHookSection(span entries, span hookSection); /** * @brief An opaque handle to a group of trapped region