// SPDX-License-Identifier: MIT /* $info$ tags: LinuxSyscalls|common desc: Glue logic, STRACE magic $end_info$ */ #pragma once #include "LinuxSyscalls/FileManagement.h" #include "LinuxSyscalls/LinuxAllocator.h" #include "LinuxSyscalls/ThreadManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _M_X86_64 #define SYSCALL_ARCH_NAME x64 #elif _M_ARM_64 #include "LinuxSyscalls/Arm64/SyscallsEnum.h" #define SYSCALL_ARCH_NAME Arm64 #endif #include "LinuxSyscalls/x64/SyscallsEnum.h" #define CONCAT_(a, b) a##b #define CONCAT(a, b) CONCAT_(a, b) #define SYSCALL_DEF(name) (HLE::SYSCALL_ARCH_NAME::CONCAT(CONCAT(SYSCALL_, SYSCALL_ARCH_NAME), _##name)) // #define DEBUG_STRACE namespace FEX { class CodeLoader; } namespace FEXCore { namespace Context { class Context; } namespace Core { struct CpuStateFrame; } } // namespace FEXCore namespace FEX::HLE { class SyscallHandler; class SignalDelegator; void RegisterEpoll(FEX::HLE::SyscallHandler* Handler); void RegisterFD(FEX::HLE::SyscallHandler* Handler); void RegisterFS(FEX::HLE::SyscallHandler* Handler); void RegisterInfo(FEX::HLE::SyscallHandler* Handler); void RegisterIO(FEX::HLE::SyscallHandler* Handler); void RegisterMemory(FEX::HLE::SyscallHandler* Handler); void RegisterNuma(FEX::HLE::SyscallHandler* Handler); void RegisterSignals(FEX::HLE::SyscallHandler* Handler); void RegisterThread(FEX::HLE::SyscallHandler* Handler); void RegisterTimer(FEX::HLE::SyscallHandler* Handler); void RegisterNotImplemented(FEX::HLE::SyscallHandler* Handler); void RegisterStubs(FEX::HLE::SyscallHandler* Handler); uint64_t UnimplementedSyscall(FEXCore::Core::CpuStateFrame* Frame, uint64_t SyscallNumber); uint64_t UnimplementedSyscallSafe(FEXCore::Core::CpuStateFrame* Frame, uint64_t SyscallNumber); struct ExecveAtArgs { int dirfd; int flags; static ExecveAtArgs Empty() { return ExecveAtArgs { .dirfd = AT_FDCWD, .flags = 0, }; } }; uint64_t ExecveHandler(const char* pathname, char* const* argv, char* const* envp, ExecveAtArgs Args); class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::SourcecodeResolver, public FEXCore::Allocator::FEXAllocOperators { public: ThreadManager TM; virtual ~SyscallHandler(); // In the case that the syscall doesn't hit the optimized path then we still need to go here uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) final override; void DefaultProgramBreak(uint64_t Base, uint64_t Size); using SyscallPtrArg0 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame); using SyscallPtrArg1 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t); using SyscallPtrArg2 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t); using SyscallPtrArg3 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t); using SyscallPtrArg4 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t, uint64_t); using SyscallPtrArg5 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); using SyscallPtrArg6 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); struct SyscallFunctionDefinition { uint8_t NumArgs; FEXCore::IR::SyscallFlags Flags; union { void* Ptr; SyscallPtrArg0 Ptr0; SyscallPtrArg1 Ptr1; SyscallPtrArg2 Ptr2; SyscallPtrArg3 Ptr3; SyscallPtrArg4 Ptr4; SyscallPtrArg5 Ptr5; SyscallPtrArg6 Ptr6; }; int32_t HostSyscallNumber; #ifdef DEBUG_STRACE fextl::string StraceFmt; #endif }; const SyscallFunctionDefinition* GetDefinition(uint64_t Syscall) { return &Definitions.at(Syscall); } FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override { auto& Def = Definitions.at(Syscall); return {Def.NumArgs, true, Def.HostSyscallNumber}; } FEXCore::IR::SyscallFlags GetSyscallFlags(uint64_t Syscall) const override { auto& Def = Definitions.at(Syscall); return Def.Flags; } virtual void RegisterSyscall_32(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags, #ifdef DEBUG_STRACE const fextl::string& TraceFormatString, #endif void* SyscallHandler, int ArgumentCount) { } virtual void RegisterSyscall_64(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags, #ifdef DEBUG_STRACE const fextl::string& TraceFormatString, #endif void* SyscallHandler, int ArgumentCount) { } uint64_t HandleBRK(FEXCore::Core::CpuStateFrame* Frame, void* Addr); FEX::HLE::FileManager FM; FEX::CodeLoader* GetCodeLoader() const { return LocalLoader; } void SetCodeLoader(FEX::CodeLoader* Loader) { LocalLoader = Loader; } FEX::HLE::SignalDelegator* GetSignalDelegator() { return SignalDelegation; } FEX_CONFIG_OPT(IsInterpreter, IS_INTERPRETER); FEX_CONFIG_OPT(IsInterpreterInstalled, INTERPRETER_INSTALLED); FEX_CONFIG_OPT(Filename, APP_FILENAME); FEX_CONFIG_OPT(RootFSPath, ROOTFS); FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); FEX_CONFIG_OPT(SMCChecks, SMCCHECKS); uint32_t GetHostKernelVersion() const { return HostKernelVersion; } uint32_t GetGuestKernelVersion() const { return GuestKernelVersion; } bool IsHostKernelVersionAtLeast(uint32_t Major, uint32_t Minor = 0, uint32_t Patch = 0) const { return GetHostKernelVersion() >= KernelVersion(Major, Minor, Patch); } static uint32_t CalculateHostKernelVersion(); uint32_t CalculateGuestKernelVersion(); static uint32_t KernelVersion(uint32_t Major, uint32_t Minor = 0, uint32_t Patch = 0) { return (Major << 24) | (Minor << 16) | Patch; } static uint32_t KernelMajor(uint32_t Version) { return Version >> 24; } static uint32_t KernelMinor(uint32_t Version) { return (Version >> 16) & 0xFF; } static uint32_t KernelPatch(uint32_t Version) { return Version & 0xFFFF; } FEX::HLE::MemAllocator* Get32BitAllocator() { return Alloc32Handler.get(); } // does a mmap as if done via a guest syscall virtual void* GuestMmap(FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags, int fd, off_t offset) = 0; // does a guest munmap as if done via a guest syscall virtual int GuestMunmap(FEXCore::Core::InternalThreadState* Thread, void* addr, uint64_t length) = 0; ///// Memory Manager tracking ///// void TrackMmap(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int Prot, int Flags, int fd, off_t Offset); void TrackMunmap(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size); void TrackMprotect(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int Prot); void TrackMremap(FEXCore::Core::InternalThreadState* Thread, uintptr_t OldAddress, size_t OldSize, size_t NewSize, int flags, uintptr_t NewAddress); void TrackShmat(FEXCore::Core::InternalThreadState* Thread, int shmid, uintptr_t Base, int shmflg); void TrackShmdt(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base); void TrackMadvise(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int advice); ///// VMA (Virtual Memory Area) tracking ///// static bool HandleSegfault(FEXCore::Core::InternalThreadState* Thread, int Signal, void* info, void* ucontext); void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override; // AOTIRCacheEntryLookupResult also includes a shared lock guard, so the pointed AOTIRCacheEntry return can be safely used FEXCore::HLE::AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestAddr) final override; ///// FORK tracking ///// void LockBeforeFork(FEXCore::Core::InternalThreadState* Thread); void UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child); SourcecodeResolver* GetSourcecodeResolver() override { return this; } void SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame) override { TM.SleepThread(CTX, Frame); } bool NeedXIDCheck() const { return NeedToCheckXID; } void DisableXIDCheck() { NeedToCheckXID = false; } constexpr static uint64_t TASK_MAX_64BIT = (1ULL << 48); protected: SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation); fextl::vector Definitions {}; std::mutex MMapMutex; // BRK management uint64_t DataSpace {}; uint64_t DataSpaceSize {}; uint64_t DataSpaceMaxSize {}; uint64_t DataSpaceStartingSize {}; // (Major << 24) | (Minor << 16) | Patch uint32_t HostKernelVersion {}; uint32_t GuestKernelVersion {}; FEXCore::Context::Context* CTX; private: FEX::HLE::SignalDelegator* SignalDelegation; std::mutex FutexMutex; std::mutex SyscallMutex; FEX::CodeLoader* LocalLoader {}; bool NeedToCheckXID {true}; #ifdef DEBUG_STRACE void Strace(FEXCore::HLE::SyscallArguments* Args, uint64_t Ret); #endif fextl::unique_ptr Alloc32Handler {}; fextl::unique_ptr GenerateMap(const std::string_view& GuestBinaryFile, const std::string_view& GuestBinaryFileId) override; ///// VMA (Virtual Memory Area) tracking ///// struct SpecialDev { static constexpr uint64_t Anon = 0x1'0000'0000; // Anonymous shared mapping, id is incrementing allocation number static constexpr uint64_t SHM = 0x2'0000'0000; // sys-v shm, id is shmid }; // Memory Resource ID // An id that can be used to identify when shared mappings actually have the same backing storage // when dev != SpecialDev::Anon, this is unique system wide struct MRID { uint64_t dev; // kernel dev_t is actually 32-bits, we use the extra bits to track SpecialDevs uint64_t id; bool operator<(const MRID& other) const { return std::tie(dev, id) < std::tie(other.dev, other.id); } }; struct VMAEntry; // Used to all MAP_SHARED VMAs of a system resource. struct MappedResource { using ContainerType = fextl::map; FEXCore::IR::AOTIRCacheEntry* AOTIRCacheEntry; VMAEntry* FirstVMA; uint64_t Length; // 0 if not fixed size ContainerType::iterator Iterator; }; union VMAProt { struct { bool Readable : 1; bool Writable : 1; bool Executable : 1; }; uint8_t All : 3; static VMAProt fromProt(int Prot); static VMAProt fromSHM(int SHMFlg); }; struct VMAFlags { bool Shared : 1; static VMAFlags fromFlags(int Flags); }; struct VMAEntry { MappedResource* Resource; // these are for Intrusive linked list tracking, starting from Resource->FirstVMA VMAEntry* ResourcePrevVMA; VMAEntry* ResourceNextVMA; uint64_t Base; uint64_t Offset; uint64_t Length; VMAFlags Flags; VMAProt Prot; }; struct VMATracking { using VMAEntry = SyscallHandler::VMAEntry; // Held while reading/writing this struct FEXCore::ForkableSharedMutex Mutex; // Memory ranges indexed by page aligned starting address fextl::map VMAs; using VMACIterator = decltype(VMAs)::const_iterator; MappedResource::ContainerType MappedResources; // Mutex must be at least shared_locked before calling VMACIterator LookupVMAUnsafe(uint64_t GuestAddr) const; // Mutex must be unique_locked before calling void SetUnsafe(FEXCore::Context::Context* Ctx, MappedResource* MappedResource, uintptr_t Base, uintptr_t Offset, uintptr_t Length, VMAFlags Flags, VMAProt Prot); // Mutex must be unique_locked before calling void ClearUnsafe(FEXCore::Context::Context* Ctx, uintptr_t Base, uintptr_t Length, MappedResource* PreservedMappedResource = nullptr); // Mutex must be unique_locked before calling void ChangeUnsafe(uintptr_t Base, uintptr_t Length, VMAProt Prot); // Mutex must be unique_locked before calling // Returns the Size fo the Shm or 0 if not found uintptr_t ClearShmUnsafe(FEXCore::Context::Context* Ctx, uintptr_t Base); private: bool ListRemove(VMAEntry* Mapping); void ListReplace(VMAEntry* Mapping, VMAEntry* NewMapping); void ListInsertAfter(VMAEntry* Mapping, VMAEntry* NewMapping); void ListPrepend(MappedResource* Resource, VMAEntry* NewVMA); static void ListCheckVMALinks(VMAEntry* VMA); } VMATracking; }; uint64_t HandleSyscall(SyscallHandler* Handler, FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args); #define SYSCALL_ERRNO() \ do { \ if (Result == -1) return -errno; \ return Result; \ } while (0) #define SYSCALL_ERRNO_NULL() \ do { \ if (Result == 0) return -errno; \ return Result; \ } while (0) extern FEX::HLE::SyscallHandler* _SyscallHandler; #ifdef DEBUG_STRACE ////// /// Templates to map parameters to format string for syscalls ////// template struct ArgToFmtString; #define ARG_TO_STR(tpy, str) \ template<> \ struct FEX::HLE::ArgToFmtString { \ inline static const char* const Format = str; \ }; // Base types ARG_TO_STR(int, "%d") ARG_TO_STR(unsigned int, "%u") ARG_TO_STR(long, "%ld") ARG_TO_STR(unsigned long, "%lu") // string types ARG_TO_STR(char*, "%s") ARG_TO_STR(const char*, "%s") // Pointers template struct ArgToFmtString { inline static const char* const Format = "%p"; }; // Use ArgToFmtString and variadic template to create a format string from an args list template fextl::string CollectArgsFmtString() { std::array array = {ArgToFmtString::Format...}; return fextl::fmt::format("{}", fmt::join(array, ", ")); } #else #define ARG_TO_STR(tpy, str) #endif struct open_how { uint64_t flags; uint64_t mode; uint64_t resolve; }; struct kernel_clone3_args { uint64_t flags; uint64_t pidfd; uint64_t child_tid; uint64_t parent_tid; uint64_t exit_signal; uint64_t stack; uint64_t stack_size; uint64_t tls; uint64_t set_tid; uint64_t set_tid_size; uint64_t cgroup; }; enum TypeOfClone { TYPE_CLONE2, TYPE_CLONE3, }; struct clone3_args { TypeOfClone Type; uint64_t SignalMask; uint64_t StackSize; void* NewStack; kernel_clone3_args args; }; uint64_t CloneHandler(FEXCore::Core::CpuStateFrame* Frame, FEX::HLE::clone3_args* args); inline static int RemapFromX86Flags(int flags) { #ifdef _M_X86_64 // Nothing to change here #elif _M_ARM_64 constexpr int X86_64_FLAG_O_DIRECT = 040000; constexpr int X86_64_FLAG_O_LARGEFILE = 0100000; constexpr int X86_64_FLAG_O_DIRECTORY = 0200000; constexpr int X86_64_FLAG_O_NOFOLLOW = 0400000; constexpr int AARCH64_FLAG_O_DIRECTORY = 040000; constexpr int AARCH64_FLAG_O_NOFOLLOW = 0100000; constexpr int AARCH64_FLAG_O_DIRECT = 0200000; constexpr int AARCH64_FLAG_O_LARGEFILE = 0400000; int new_flags {}; if (flags & X86_64_FLAG_O_DIRECT) { flags = (flags & ~X86_64_FLAG_O_DIRECT); new_flags |= AARCH64_FLAG_O_DIRECT; } if (flags & X86_64_FLAG_O_LARGEFILE) { flags = (flags & ~X86_64_FLAG_O_LARGEFILE); new_flags |= AARCH64_FLAG_O_LARGEFILE; } if (flags & X86_64_FLAG_O_DIRECTORY) { flags = (flags & ~X86_64_FLAG_O_DIRECTORY); new_flags |= AARCH64_FLAG_O_DIRECTORY; } if (flags & X86_64_FLAG_O_NOFOLLOW) { flags = (flags & ~X86_64_FLAG_O_NOFOLLOW); new_flags |= AARCH64_FLAG_O_NOFOLLOW; } flags |= new_flags; #else #error Unknown flag remappings for this host platform #endif return flags; } inline static int RemapToX86Flags(int flags) { #ifdef _M_X86_64 // Nothing to change here #elif _M_ARM_64 constexpr int X86_64_FLAG_O_DIRECT = 040000; constexpr int X86_64_FLAG_O_LARGEFILE = 0100000; constexpr int X86_64_FLAG_O_DIRECTORY = 0200000; constexpr int X86_64_FLAG_O_NOFOLLOW = 0400000; constexpr int AARCH64_FLAG_O_DIRECTORY = 040000; constexpr int AARCH64_FLAG_O_NOFOLLOW = 0100000; constexpr int AARCH64_FLAG_O_DIRECT = 0200000; constexpr int AARCH64_FLAG_O_LARGEFILE = 0400000; int new_flags {}; if (flags & AARCH64_FLAG_O_DIRECT) { flags = (flags & ~AARCH64_FLAG_O_DIRECT); new_flags |= X86_64_FLAG_O_DIRECT; } if (flags & AARCH64_FLAG_O_LARGEFILE) { flags = (flags & ~AARCH64_FLAG_O_LARGEFILE); new_flags |= X86_64_FLAG_O_LARGEFILE; } if (flags & AARCH64_FLAG_O_DIRECTORY) { flags = (flags & ~AARCH64_FLAG_O_DIRECTORY); new_flags |= X86_64_FLAG_O_DIRECTORY; } if (flags & AARCH64_FLAG_O_NOFOLLOW) { flags = (flags & ~AARCH64_FLAG_O_NOFOLLOW); new_flags |= X86_64_FLAG_O_NOFOLLOW; } flags |= new_flags; #else #error Unknown flag remappings for this host platform #endif return flags; } /** * @brief Checks raw syscall return for error * * This should only be used with raw syscall usage * * This should not be used with glibc wrapped syscall functions * - This includes the glibc ::syscall(...) function * - This is due to glibc already wrapping the return and setting errno * * This function should not be used with UAPI breaking syscall results * ioctl specifically will break this convention. * * @param Result The raw syscall return * * @return If the result was an error result */ [[maybe_unused]] static bool HasSyscallError(uint64_t Result) { // MAX_ERRNO is part of the Linux Syscall ABI // Redefined here since it doesn't exist as a visible define in the UAPI headers constexpr uint64_t MAX_ERRNO = 0xFFFF'FFFF'FFFF'0001ULL; // Raw syscalls are guaranteed to not return a valid result in the range of [-4095, -1] // In cases where FEX needs to use raw syscalls, this helper checks for this idiom return reinterpret_cast(Result) >= MAX_ERRNO; } [[maybe_unused]] static bool HasSyscallError(const void* Result) { return HasSyscallError(reinterpret_cast(Result)); } template uint64_t GetDentsEmulation(int fd, T* dirp, uint32_t count); namespace FaultSafeMemcpy { // These are little helper functions for cases when FEX needs to copy data to or from the application in a robust fashion. // CopyFromUser and CopyToUser are memcpy routines that expect to safely SIGSEGV when reading or writing application memory respectively. // Returns zero if the memcpy completed, or crashes with SIGABRT and a log message if it faults. [[nodiscard]] size_t CopyFromUser(void* Dest, const void* Src, size_t Size); [[nodiscard]] size_t CopyToUser(void* Dest, const void* Src, size_t Size); bool IsFaultLocation(uint64_t PC); } // namespace FaultSafeMemcpy } // namespace FEX::HLE // Registers syscall for both 32bit and 64bit #define REGISTER_SYSCALL_IMPL(name, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, ~0, FEXCore::IR::SyscallFlags::DEFAULT, lambda) #define REGISTER_SYSCALL_IMPL_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, ~0, flags, lambda) #define REGISTER_SYSCALL_IMPL_PASS_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, SYSCALL_DEF(name), flags, lambda) #define REGISTER_SYSCALL_IMPL_INTERNAL(name, number, flags, lambda) \ do { \ FEX::HLE::x64::RegisterSyscall(Handler, FEX::HLE::x64::SYSCALL_x64_##name, (number), (flags), #name, (lambda)); \ FEX::HLE::x32::RegisterSyscall(Handler, FEX::HLE::x32::SYSCALL_x86_##name, (number), (flags), #name, (lambda)); \ } while (false)