mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-26 17:34:17 +01:00
Implement Symbol Hooking
Symbol hooking is required for HLE implementations of certain features in the future such as `nvdec` and for more in-depth debugging of games as we can inspect them on a SDK function level which allows us to debug issues far more easily.
This commit is contained in:
parent
8892eb08e6
commit
f4a8328cef
@ -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
|
||||
|
@ -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"),
|
||||
|
17
app/src/main/cpp/skyline/hle/symbol_hook_table.h
Normal file
17
app/src/main/cpp/skyline/hle/symbol_hook_table.h
Normal file
@ -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<HookTableEntry, 0> HookedSymbols{};
|
||||
}
|
16
app/src/main/cpp/skyline/hle/symbol_hooks.cpp
Normal file
16
app/src/main/cpp/skyline/hle/symbol_hooks.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include "symbol_hooks.h"
|
||||
|
||||
namespace skyline::hle {
|
||||
std::string Demangle(const std::string_view mangledName) {
|
||||
int status{};
|
||||
size_t length{};
|
||||
std::unique_ptr<char, decltype(&std::free)> 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)} {}
|
||||
}
|
29
app/src/main/cpp/skyline/hle/symbol_hooks.h
Normal file
29
app/src/main/cpp/skyline/hle/symbol_hooks.h
Normal file
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::hle {
|
||||
struct HookedSymbol;
|
||||
|
||||
using HookFunction = std::function<void(const DeviceState &, const HookedSymbol &)>;
|
||||
|
||||
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<OverrideHook, EntryExitHook>;
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
@ -6,6 +6,27 @@
|
||||
#include <common.h>
|
||||
|
||||
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<u32>("MOD0")};
|
||||
return magic == MODMagic;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The contents of an executable binary abstracted away from the derivatives of Loader
|
||||
*/
|
||||
|
@ -7,55 +7,111 @@
|
||||
#include <os.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <hle/symbol_hook_table.h>
|
||||
#include "loader.h"
|
||||
|
||||
namespace skyline::loader {
|
||||
Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name) {
|
||||
Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name, bool dynamicallyLinked) {
|
||||
u8 *base{reinterpret_cast<u8 *>(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<kernel::type::KPrivateMemory>(span<u8>{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<Elf64_Sym *>(executable.ro.contents.data() + executable.dynsym.offset), executable.dynsym.size / sizeof(Elf64_Sym)};
|
||||
span dynstr{reinterpret_cast<char *>(executable.ro.contents.data() + executable.dynstr.offset), executable.dynstr.size};
|
||||
std::vector<nce::NCE::HookedSymbolEntry> executableSymbols;
|
||||
size_t hookSize{};
|
||||
if (dynamicallyLinked) {
|
||||
for (auto &symbol : dynsym) {
|
||||
if (symbol.st_name == 0 || symbol.st_value == 0)
|
||||
continue;
|
||||
|
||||
process->NewHandle<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<u32 *>(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};
|
||||
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
|
||||
}
|
||||
|
||||
hookSize = util::AlignUp(state.nce->GetHookSectionSize(executableSymbols), PAGE_SIZE);
|
||||
}
|
||||
|
||||
process->NewHandle<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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<kernel::type::KPrivateMemory>(span<u8>{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,
|
||||
.programStart = base + patch.size,
|
||||
.hookStart = base + patch.size,
|
||||
.programStart = executableBase,
|
||||
.programEnd = base + size,
|
||||
.name = name,
|
||||
.patchName = name + ".patch",
|
||||
.symbols = span(reinterpret_cast<Elf64_Sym *>(rodataOffset + executable.dynsym.offset), executable.dynsym.size / sizeof(Elf64_Sym)),
|
||||
.symbolStrings = span(reinterpret_cast<char *>(rodataOffset + executable.dynstr.offset), executable.dynstr.size),
|
||||
.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; }), symbolicInfo);
|
||||
executables.insert(std::upper_bound(executables.begin(), executables.end(), base, [](void *ptr, const ExecutableSymbolicInfo &it) { return ptr < it.patchStart; }), std::move(symbolicInfo));
|
||||
}
|
||||
|
||||
return {base, size, base + patch.size};
|
||||
state.nce->PatchCode(executable.text.contents, reinterpret_cast<u32 *>(base), patch.size, patch.offsets, hookSize);
|
||||
if (hookSize)
|
||||
state.nce->WriteHookSection(executableSymbols, span<u8>{base + patch.size, hookSize}.cast<u32>());
|
||||
|
||||
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};
|
||||
}
|
||||
|
@ -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<Elf64_Sym> symbols; //!< A span over the .dynsym section
|
||||
span<char> symbolStrings; //!< A span over the .dynstr section
|
||||
std::string hookName; //!< The name of the hook section
|
||||
std::vector<Elf64_Sym> symbols; //!< A span over the .dynsym section
|
||||
std::vector<char> symbolStrings; //!< A span over the .dynstr section
|
||||
};
|
||||
|
||||
std::vector<ExecutableSymbolicInfo> 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<kernel::type::KProcess> &process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string &name = {});
|
||||
ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string &name = {}, bool dynamicallyLinked = false);
|
||||
|
||||
std::optional<vfs::NACP> nacp;
|
||||
std::shared_ptr<vfs::Backing> romFs;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace skyline::loader {
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, size_t offset, const std::string &name) {
|
||||
Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, size_t offset, const std::string &name, bool dynamicallyLinked) {
|
||||
auto header{backing->Read<NsoHeader>()};
|
||||
|
||||
if (header.magic != util::MakeMagic<u32>("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<kernel::type::KProcess> &process, const DeviceState &state) {
|
||||
|
@ -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<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, size_t offset = 0, const std::string &name = {});
|
||||
static ExecutableLoadInfo LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, size_t offset = 0, const std::string &name = {}, bool dynamicallyLinked = false);
|
||||
|
||||
void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state) override;
|
||||
};
|
||||
|
@ -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<u8> &text) {
|
||||
size_t size{guest::SaveCtxSize + guest::LoadCtxSize + MainSvcTrampolineSize};
|
||||
size_t size{guest::SaveCtxSize + guest::LoadCtxSize + TrampolineSize};
|
||||
std::vector<size_t> offsets;
|
||||
|
||||
u64 frequency;
|
||||
@ -246,46 +347,14 @@ namespace skyline::nce {
|
||||
return {util::AlignUp(size * sizeof(u32), constant::PageSize), offsets};
|
||||
}
|
||||
|
||||
void NCE::PatchCode(std::vector<u8> &text, u32 *patch, size_t patchSize, const std::vector<size_t> &offsets) {
|
||||
void NCE::PatchCode(std::vector<u8> &text, u32 *patch, size_t patchSize, const std::vector<size_t> &offsets, size_t textOffset) {
|
||||
u32 *start{patch};
|
||||
u32 *end{patch + (patchSize / sizeof(u32))};
|
||||
|
||||
std::memcpy(patch, reinterpret_cast<void *>(&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<u64>(&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<u64>(&NCE::SvcHandler));
|
||||
|
||||
std::memcpy(patch, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32));
|
||||
patch += guest::LoadCtxSize;
|
||||
@ -299,7 +368,7 @@ namespace skyline::nce {
|
||||
auto svc{*reinterpret_cast<instructions::Svc *>(instruction)};
|
||||
auto mrs{*reinterpret_cast<instructions::Mrs *>(instruction)};
|
||||
auto msr{*reinterpret_cast<instructions::Msr *>(instruction)};
|
||||
auto endOffset{[&] { return static_cast<size_t>(end - patch); }};
|
||||
auto endOffset{[&] { return static_cast<size_t>(end - patch) + (textOffset / sizeof(u32)); }};
|
||||
auto startOffset{[&] { return static_cast<size_t>(start - patch); }};
|
||||
|
||||
if (svc.Verify()) {
|
||||
@ -318,7 +387,7 @@ namespace skyline::nce {
|
||||
patch++;
|
||||
|
||||
/* Restore Context and Return */
|
||||
*patch = instructions::BL(static_cast<i32>(startOffset() + guest::SaveCtxSize + MainSvcTrampolineSize)).raw;
|
||||
*patch = instructions::BL(static_cast<i32>(startOffset() + guest::SaveCtxSize + TrampolineSize)).raw;
|
||||
patch++;
|
||||
*patch++ = 0xF84107FE; // LDR LR, [SP], #16
|
||||
*patch = instructions::B(static_cast<i32>(endOffset() + offset + 1)).raw;
|
||||
@ -406,6 +475,95 @@ namespace skyline::nce {
|
||||
}
|
||||
}
|
||||
|
||||
size_t NCE::GetHookSectionSize(span<HookedSymbolEntry> entries) {
|
||||
size_t size{guest::SaveCtxSize + guest::LoadCtxSize + TrampolineSize};
|
||||
for (const auto &entry : entries) {
|
||||
constexpr size_t EmitTrampolineSize{10};
|
||||
if (std::holds_alternative<hle::OverrideHook>(entry.hook))
|
||||
size += EmitTrampolineSize + 1;
|
||||
else if (std::holds_alternative<hle::EntryExitHook>(entry.hook))
|
||||
size += 4 + EmitTrampolineSize + 1 + EmitTrampolineSize + 4 + 1;
|
||||
}
|
||||
return size * sizeof(u32);
|
||||
}
|
||||
|
||||
void NCE::WriteHookSection(span<HookedSymbolEntry> entries, span<u32> hookSection) {
|
||||
u32 *start{hookSection.data()};
|
||||
u32 *end{hookSection.end().base()};
|
||||
u32 *hook{start};
|
||||
|
||||
std::memcpy(hook, reinterpret_cast<void *>(&guest::SaveCtx), guest::SaveCtxSize * sizeof(u32));
|
||||
hook += guest::SaveCtxSize;
|
||||
|
||||
hook = WriteTrampoline(hook, reinterpret_cast<u64>(&NCE::HookHandler));
|
||||
|
||||
std::memcpy(hook, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32));
|
||||
hook += guest::LoadCtxSize;
|
||||
|
||||
u64 hookIndex{static_cast<u64>(hookedSymbols.size())};
|
||||
hookedSymbols.reserve(entries.size());
|
||||
for (const auto &entry : entries) {
|
||||
auto startOffset{[&] { return static_cast<size_t>(start - hook); }};
|
||||
auto endOffset{[&] { return static_cast<size_t>(end - hook); }};
|
||||
auto emitTrampoline{[&](HookId hookId) {
|
||||
/* Save Context */
|
||||
*hook++ = 0xF81F0FFE; // STR LR, [SP, #-16]!
|
||||
*hook = instructions::BL(static_cast<i32>(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<i32>(startOffset() + guest::SaveCtxSize)).raw; // BL HookTrampoline
|
||||
hook++;
|
||||
|
||||
/* Restore Context */
|
||||
*hook = instructions::BL(static_cast<i32>(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<hle::OverrideHook>(entry.hook)) {
|
||||
/* Override Hook */
|
||||
emitTrampoline(HookId{hookIndex, false});
|
||||
} else if (std::holds_alternative<hle::EntryExitHook>(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<i32>(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<TrapMap::Interval> &intervals, TrapProtection protection) {
|
||||
|
@ -4,7 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <linux/elf.h>
|
||||
#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<hle::HookedSymbol> 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<u8> &text, u32 *patch, size_t patchSize, const std::vector<size_t> &offsets);
|
||||
static void PatchCode(std::vector<u8> &text, u32 *patch, size_t patchSize, const std::vector<size_t> &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<HookedSymbolEntry> entries);
|
||||
|
||||
void WriteHookSection(span<HookedSymbolEntry> entries, span<u32> hookSection);
|
||||
|
||||
/**
|
||||
* @brief An opaque handle to a group of trapped region
|
||||
|
Loading…
Reference in New Issue
Block a user