// SPDX-License-Identifier: MIT /* $info$ tags: LinuxSyscalls|common desc: Glue logic, STRACE magic $end_info$ */ #pragma once #include "Common/Linux/LinuxVersion.h" #include "Common/VolatileMetadata.h" #include "LinuxSyscalls/FileManagement.h" #include "LinuxSyscalls/LinuxAllocator.h" #include "LinuxSyscalls/ThreadManager.h" #include "LinuxSyscalls/Seccomp/SeccompEmulator.h" #include "LinuxSyscalls/SyscallsVMATracking.h" #include "ArchHelpers/MContext.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ARCHITECTURE_x86_64 #define SYSCALL_ARCH_NAME x64 #elif ARCHITECTURE_arm64 #include "LinuxSyscalls/Arm64/SyscallsEnum.h" #define SYSCALL_ARCH_NAME Arm64 #endif #include "LinuxSyscalls/x64/SyscallsEnum.h" #include "LinuxSyscalls/x32/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 { struct SyscallArguments { static constexpr std::size_t MAX_ARGS = 7; uint64_t Argument[MAX_ARGS]; }; class SyscallHandler; class SignalDelegator; class ThunkHandler; 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 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(FEXCore::Core::CpuStateFrame* Frame, const char* pathname, char* const* argv, char* const* envp, ExecveAtArgs Args); class SyscallMmapInterface { public: // 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 uint64_t GuestMunmap(FEXCore::Core::InternalThreadState* Thread, void* addr, uint64_t length) = 0; virtual void AddVirtualPage(FEXCore::Core::InternalThreadState* Thread, uint64_t addr, size_t length, int prot) = 0; }; class SyscallHandler : public FEXCore::HLE::SyscallHandler, public SyscallMmapInterface, FEXCore::HLE::SourcecodeResolver, public FEXCore::CodeMapOpener, public FEXCore::Allocator::FEXAllocOperators { public: ThreadManager TM; FEX::HLE::SeccompEmulator SeccompEmulator; virtual ~SyscallHandler(); void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) final override; void DefaultProgramBreak(uint64_t Base, uint64_t Size); void DeserializeSeccompFD(FEX::HLE::ThreadStateObject* Thread, int FD) { if (FD == -1) { return; } SeccompEmulator.DeserializeFilters(Thread->Thread->CurrentFrame, FD); } 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 { union { void* Ptr; SyscallPtrArg0 Ptr0; SyscallPtrArg1 Ptr1; SyscallPtrArg2 Ptr2; SyscallPtrArg3 Ptr3; SyscallPtrArg4 Ptr4; SyscallPtrArg5 Ptr5; SyscallPtrArg6 Ptr6; }; uint8_t NumArgs; #ifdef DEBUG_STRACE fextl::string StraceFmt; #endif }; const SyscallFunctionDefinition* GetDefinition(uint64_t Syscall) { return &Definitions.at(Syscall); } virtual void RegisterSyscall_32(int SyscallNumber, #ifdef DEBUG_STRACE const fextl::string& TraceFormatString, #endif void* SyscallHandler, int ArgumentCount) { } virtual void RegisterSyscall_64(int SyscallNumber, #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::HLE::ThunkHandler* GetThunkHandler() { return ThunkHandler; } 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); FEX_CONFIG_OPT(NeedsSeccomp, NEEDSSECCOMP); FEX_CONFIG_OPT(EnableCodeCaching, ENABLECODECACHINGWIP); 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() >= LinuxVersion::KernelVersion(Major, Minor, Patch); } uint32_t CalculateGuestKernelVersion(); virtual FEX::HLE::MemAllocator* Get32BitAllocator() { return Alloc32Handler.get(); } // does a mmap as if done via a guest syscall void* GuestMmap(bool Is64Bit, FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags, int fd, off_t offset); using SyscallMmapInterface::GuestMmap; // does a guest munmap as if done via a guest syscall uint64_t GuestMunmap(bool Is64Bit, FEXCore::Core::InternalThreadState* Thread, void* addr, uint64_t length); using SyscallMmapInterface::GuestMunmap; uint64_t GuestMremap(bool Is64Bit, FEXCore::Core::InternalThreadState*, void* old_address, size_t old_size, size_t new_size, int flags, void* new_address); uint64_t GuestMprotect(FEXCore::Core::InternalThreadState*, void* addr, size_t len, int prot); uint64_t GuestShmat(bool Is64Bit, FEXCore::Core::InternalThreadState*, int shmid, const void* shmaddr, int shmflg); uint64_t GuestShmdt(bool Is64Bit, FEXCore::Core::InternalThreadState*, const void* shmaddr); ///// Memory Manager tracking ///// struct LateApplyExtendedVolatileMetadata { fextl::set VolatileInstructions {}; FEXCore::IntervalList VolatileValidRanges {}; }; std::optional TrackMmap(FEXCore::Core::InternalThreadState* Thread, uint64_t addr, size_t length, int prot, int flags, int fd, off_t offset, std::optional& CachedSection); void AddVirtualPage(FEXCore::Core::InternalThreadState* Thread, uint64_t addr, size_t length, int prot) override; using SyscallMmapInterface::AddVirtualPage; void TrackMunmap(FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length); void TrackMremap(FEXCore::Core::InternalThreadState* Thread, uint64_t OldAddress, size_t OldSize, size_t NewSize, int flags, uint64_t NewAddress); void TrackShmat(FEXCore::Core::InternalThreadState* Thread, int shmid, uint64_t shmaddr, int shmflg, uint64_t Length); uint64_t TrackShmdt(FEXCore::Core::InternalThreadState* Thread, uint64_t shmaddr); void TrackMprotect(FEXCore::Core::InternalThreadState* Thread, void* addr, size_t len, int prot); void TrackMadvise(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int advice); void InvalidateCodeRangeIfNecessary(FEXCore::Core::InternalThreadState* Thread, uint64_t Base, uint64_t Length, bool CheckPendingVMAResources) { if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) { TM.InvalidateGuestCodeRange(Thread, Base, Length); } if (CheckPendingVMAResources && Thread) { auto lk = FEXCore::GuardSignalDeferringSection(VMATracking.Mutex, Thread); VMATracking.FlushPendingResourceDeletions(); } } void InvalidateCodeRangeIfNecessaryOnRemap(FEXCore::Core::InternalThreadState* Thread, uint64_t OldAddress, uint64_t NewAddress, size_t OldSize, size_t NewSize) { if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) { if (OldAddress != NewAddress) { if (OldSize != 0) { // This also handles the MREMAP_DONTUNMAP case TM.InvalidateGuestCodeRange(Thread, OldAddress, OldSize); } } else { // If mapping shrunk, flush the unmapped region if (OldSize > NewSize) { TM.InvalidateGuestCodeRange(Thread, OldAddress + NewSize, OldSize - NewSize); } } } } ///// 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; void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override; std::optional LookupExecutableFileSection(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestAddr) final override; void TriggerGuestLibWrapperCodeCacheLoad(FEXCore::Core::InternalThreadState&, uint64_t AnyAddr); int OpenCodeMapFile() override; FEXCore::HLE::ExecutableRangeInfo QueryGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Address) override; ///// FORK tracking ///// void LockBeforeFork(FEXCore::Core::InternalThreadState* Thread); void UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child); void RegisterTLSState(FEX::HLE::ThreadStateObject* Thread); void UninstallTLSState(FEX::HLE::ThreadStateObject* Thread); 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); constexpr static size_t MAX_LDT_ENTRIES = 8192; constexpr static size_t LDT_ENTRY_SIZE = sizeof(FEXCore::Core::CPUState::gdt_segment); VMATracking::VMATracking VMATracking; const uint64_t CodeCacheConfigId = 0; // TODO: Make unique to active configuration uint64_t read_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount); uint64_t write_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount, bool legacy); protected: SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler); fextl::vector Definitions {std::max(FEX::HLE::x64::SYSCALL_x64_MAX, FEX::HLE::x32::SYSCALL_x86_MAX), { .Ptr = reinterpret_cast(&UnimplementedSyscall), .NumArgs = 255, }}; std::mutex MMapMutex; // BRK management uint64_t DataSpace {}; uint64_t DataSpaceSize {}; uint64_t DataSpaceMappedSize {}; // (Major << 24) | (Minor << 16) | Patch uint32_t HostKernelVersion {}; uint32_t GuestKernelVersion {}; FEXCore::Context::Context* CTX; private: FEX::HLE::SignalDelegator* SignalDelegation; FEX::HLE::ThunkHandler* ThunkHandler; fextl::unordered_map ExtendedMetaData {}; std::mutex FutexMutex; std::mutex SyscallMutex; // std::mutex CodeCachePatchingMutex; FEXCore::ForkableUniqueMutex CodeCachePatchingMutex; FEX::CodeLoader* LocalLoader {}; bool NeedToCheckXID {true}; #ifdef DEBUG_STRACE void Strace(FEXCore::Core::CpuStateFrame* Frame, uint64_t Ret); #endif fextl::unique_ptr GenerateMap(std::string_view GuestBinaryFile, std::string_view GuestBinaryFileId) override; fextl::unique_ptr Alloc32Handler {}; std::atomic AnonSharedId {1}; static inline uint64_t GetArg(bool Is64Bit, FEXCore::Core::CpuStateFrame* Frame, size_t Arg) { constexpr size_t SyscallArgs = 7; using SyscallArray = std::array; static constexpr SyscallArray GPRIndexes_64 = { FEXCore::X86State::REG_RAX, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDX, FEXCore::X86State::REG_R10, FEXCore::X86State::REG_R8, FEXCore::X86State::REG_R9, }; static constexpr SyscallArray GPRIndexes_32 = { FEXCore::X86State::REG_RAX, FEXCore::X86State::REG_RBX, FEXCore::X86State::REG_RCX, FEXCore::X86State::REG_RDX, FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RBP, }; const auto Index = Is64Bit ? GPRIndexes_64[Arg] : GPRIndexes_32[Arg]; const auto Mask = Is64Bit ? ~0ULL : ~0U; const auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); return Thread->Thread->CurrentFrame->State.gregs[Index] & Mask; }; template void HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC); }; #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, "{}") ARG_TO_STR(unsigned int, "{}") ARG_TO_STR(long, "{}") ARG_TO_STR(unsigned long, "{}") // string types ARG_TO_STR(char*, "{}") ARG_TO_STR(const char*, "{}") // Pointers template struct ArgToFmtString { inline static const char* const Format = "{:x}"; }; // 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 ARCHITECTURE_x86_64 // Nothing to change here #elif ARCHITECTURE_arm64 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 ARCHITECTURE_x86_64 // Nothing to change here #elif ARCHITECTURE_arm64 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 FaultSafeUserMemAccess { // 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); #if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED && defined(ARCHITECTURE_arm64) // These helpers just check if the user pointer is readable and writable. // This is useful in an assert build that can be safely sprinkled through the syscall handler without overhead in release builds. void VerifyIsReadable(const void* Src, size_t Size); void VerifyIsReadableOrNull(const void* Src, size_t Size); void VerifyIsWritable(void* Src, size_t Size); void VerifyIsWritableOrNull(void* Src, size_t Size); // Iterates a null-terminated string and checks if all bytes are readable void VerifyIsStringReadable(const char* Src); // Iterates a null-terminated string and checks if all bytes are readable. Up to MaxSize bytes are checked. void VerifyIsStringReadableMaxSize(const char* Src, size_t MaxSize); #else inline void VerifyIsReadable(const void* Src, size_t Size) { if (Src == nullptr) { ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument"); } } inline void VerifyIsReadableOrNull(const void* Src, size_t Size) {} inline void VerifyIsWritable(void* Src, size_t Size) { if (Src == nullptr) { ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument"); } } inline void VerifyIsWritableOrNull(void* Src, size_t Size) {} inline void VerifyIsStringReadable(const char* Src) { if (Src == nullptr) { ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument"); } } inline void VerifyIsStringReadableMaxSize(const char* Src, size_t MaxSize) { if (Src == nullptr) { ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument"); } } #endif bool IsFaultLocation(uint64_t PC); static inline bool TryHandleSafeFault(int Signal, const siginfo_t& SigInfo, void* UContext) { if (Signal == SIGSEGV && (SigInfo.si_code == SEGV_MAPERR || SigInfo.si_code == SEGV_ACCERR) && FaultSafeUserMemAccess::IsFaultLocation(ArchHelpers::Context::GetPc(UContext))) { // Return from the subroutine, returning EFAULT. ArchHelpers::Context::SetArmReg(UContext, 0, EFAULT); ArchHelpers::Context::SetPc(UContext, ArchHelpers::Context::GetArmReg(UContext, 30)); return true; } return false; } } // namespace FaultSafeUserMemAccess template inline static uint64_t futimesat_compat(int dirfd, const char* pathname, const T times[2]) { FaultSafeUserMemAccess::VerifyIsReadableOrNull(times, sizeof(*times) * 2); timespec tvs[2] {}; timespec* tv_ptr {}; if (times) { constexpr int64_t ONE_SECOND_AS_USEC = 1'000'000LL; // Incoming microsecond time must not be negative or be larger than one second. if (times[0].tv_usec < 0 || times[1].tv_usec < 0 || times[0].tv_usec >= ONE_SECOND_AS_USEC || times[1].tv_usec >= ONE_SECOND_AS_USEC) { return -EINVAL; } tvs[0].tv_sec = times[0].tv_sec; tvs[0].tv_nsec = 1000LL * times[0].tv_usec; tvs[1].tv_sec = times[1].tv_sec; tvs[1].tv_nsec = 1000LL * times[1].tv_usec; tv_ptr = tvs; } uint64_t Result = ::syscall(SYSCALL_DEF(utimensat), dirfd, pathname, tv_ptr, 0); SYSCALL_ERRNO(); } } // namespace FEX::HLE // Registers syscall for both 32bit and 64bit #define REGISTER_SYSCALL_IMPL(name, lambda) \ do { \ FEX::HLE::x64::RegisterSyscall(Handler, FEX::HLE::x64::SYSCALL_x64_##name, #name, (lambda)); \ FEX::HLE::x32::RegisterSyscall(Handler, FEX::HLE::x32::SYSCALL_x86_##name, #name, (lambda)); \ } while (false)