// SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #pragma once #include #include #include "common.h" #include "hle/symbol_hooks.h" #include "common/interval_map.h" namespace skyline::nce { /** * @brief The NCE (Native Code Execution) class is responsible for managing state relevant to the layer between the host and guest */ class 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 */ enum class TrapProtection { None = 0, //!< No protection is required WriteOnly = 1, //!< Only write protection is required ReadWrite = 2, //!< Both read and write protection are required }; using TrapCallback = std::function; using LockCallback = std::function; struct CallbackEntry { TrapProtection protection; //!< The least restrictive protection that this callback needs to have LockCallback lockCallback; TrapCallback readCallback, writeCallback; CallbackEntry(TrapProtection protection, LockCallback lockCallback, TrapCallback readCallback, TrapCallback writeCallback); }; std::mutex trapMutex; //!< Synchronizes the accesses to the trap map using TrapMap = IntervalMap; TrapMap trapMap; //!< A map of all intervals and corresponding callbacks that have been registered /** * @brief Reprotects the intervals to the least restrictive protection given the supplied protection */ void ReprotectIntervals(const std::vector& intervals, TrapProtection protection); bool TrapHandler(u8* address, bool write); 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 * @note Exiting must not be performed directly as it could leak temporary objects on the stack by not calling their destructors */ struct ExitException : std::exception { bool killAllThreads; //!< If to kill all threads or just the throwing thread ExitException(bool killAllThreads = true); virtual const char *what() const noexcept; }; /** * @brief Handles any signals in the NCE threads */ static void SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls); /** * @brief Handles signals for any host threads which may access NCE trapped memory * @note Any untrapped SIGSEGVs will emit SIGTRAP when a debugger is attached rather than throwing an exception */ static void HostSignalHandler(int signal, siginfo *info, ucontext *ctx); /** * @note There should only be one instance of NCE concurrently */ NCE(const DeviceState &state); ~NCE(); struct PatchData { size_t size; //!< Size of the .patch section std::vector offsets; //!< Offsets in .text of instructions that need to be patched }; static PatchData GetPatchData(const std::vector &text); /** * @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, size_t textOffset = 0); static size_t GetHookSectionSize(span entries); void WriteHookSection(span entries, span hookSection); /** * @brief An opaque handle to a group of trapped region */ class TrapHandle : private TrapMap::GroupHandle { constexpr TrapHandle(const TrapMap::GroupHandle &handle); friend NCE; }; /** * @brief Creates a region of guest memory that can be trapped with a callback for when an access to it has been made * @param lockCallback A callback to lock the resource that is being trapped, it must block until the resource is locked but unlock it prior to returning * @param readCallback A callback for read accesses to the trapped region, it must not block and return a boolean if it would block * @param writeCallback A callback for write accesses to the trapped region, it must not block and return a boolean if it would block * @note The handle **must** be deleted using DeleteTrap before the NCE instance is destroyed * @note It is UB to supply a region of host memory rather than guest memory * @note This doesn't trap the region in itself, any trapping must be done via TrapRegions(...) */ TrapHandle CreateTrap(span> regions, const LockCallback& lockCallback, const TrapCallback& readCallback, const TrapCallback& writeCallback); /** * @brief Re-traps a region of memory after protections were removed * @param writeOnly If the trap is optimally for write-only accesses, this is not guarenteed */ void TrapRegions(TrapHandle handle, bool writeOnly); /** * @brief Removes protections from a region of memory */ void RemoveTrap(TrapHandle handle); /** * @brief Deletes a trap handle and removes the protection from the region */ void DeleteTrap(TrapHandle handle); }; }