mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-09 22:00:19 +02:00
Reimagining of #3355 without any json generators or new concepts. Fixes some mislabeling of system calls. Some getting inlined when they shouldn't be, a lot not getting inlined when they can be. This really cleans up the syscall implementation, all syscalls that can be passthrough implementations require a very small two line declaration. Additionally cleans up a bit of implementation cruft where some passthrough syscalls were using the glibc syscall handler, and some were using the glibc implementation. We have had multiple issues in the past where the glibc implementation does something subtly different than the raw syscall and breaks things. Now all passthrough handlers do a system call directly, removing at least one indirection and some ambiguity. This makes it significantly easier to add new passthrough syscalls as well. Only need to do a version check and add the three lines per syscall. Which there are new syscalls incoming that we will want to add. Tangible improvements: - Syscalls are lower overhead than ever. - When I'm adding more syscalls I have less chance of mucking it up.
641 lines
22 KiB
C++
641 lines
22 KiB
C++
// 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 <FEXCore/Config/Config.h>
|
|
#include <FEXCore/HLE/SyscallHandler.h>
|
|
#include <FEXCore/HLE/SourcecodeResolver.h>
|
|
#include <FEXCore/IR/IR.h>
|
|
#include <FEXCore/Utils/CompilerDefs.h>
|
|
#include <FEXCore/Utils/SignalScopeGuards.h>
|
|
#include <FEXCore/fextl/fmt.h>
|
|
#include <FEXCore/fextl/map.h>
|
|
#include <FEXCore/fextl/memory.h>
|
|
#include <FEXCore/fextl/string.h>
|
|
#include <FEXCore/fextl/vector.h>
|
|
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <type_traits>
|
|
#include <list>
|
|
#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 FEXCore {
|
|
class CodeLoader;
|
|
namespace Context {
|
|
class Context;
|
|
}
|
|
namespace Core {
|
|
struct CpuStateFrame;
|
|
}
|
|
}
|
|
|
|
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 ThreadManager final {
|
|
public:
|
|
ThreadManager(FEXCore::Context::Context *CTX, FEX::HLE::SignalDelegator *SignalDelegation)
|
|
: CTX {CTX}
|
|
, SignalDelegation {SignalDelegation} {}
|
|
|
|
~ThreadManager();
|
|
|
|
FEXCore::Core::InternalThreadState *CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState = nullptr, uint64_t ParentTID = 0);
|
|
void TrackThread(FEXCore::Core::InternalThreadState *Thread) {
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
Threads.emplace_back(Thread);
|
|
}
|
|
|
|
void DestroyThread(FEXCore::Core::InternalThreadState *Thread);
|
|
void StopThread(FEXCore::Core::InternalThreadState *Thread);
|
|
void RunThread(FEXCore::Core::InternalThreadState *Thread);
|
|
|
|
void Pause();
|
|
void Run();
|
|
void Step();
|
|
void Stop(bool IgnoreCurrentThread = false);
|
|
|
|
void WaitForIdle();
|
|
void WaitForIdleWithTimeout();
|
|
void WaitForThreadsToRun();
|
|
|
|
void SleepThread(FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame);
|
|
|
|
void UnlockAfterFork(FEXCore::Core::InternalThreadState *Thread, bool Child);
|
|
|
|
void IncrementIdleRefCount() {
|
|
++IdleWaitRefCount;
|
|
}
|
|
|
|
void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState *CallingThread, uint64_t Start, uint64_t Length) {
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
|
|
// Potential deferred since Thread might not be valid.
|
|
// Thread object isn't valid very early in frontend's initialization.
|
|
// To be more optimal the frontend should provide this code with a valid Thread object earlier.
|
|
auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread);
|
|
|
|
for (auto &Thread : Threads) {
|
|
CTX->InvalidateGuestCodeRange(Thread, Start, Length);
|
|
}
|
|
}
|
|
|
|
void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState *CallingThread, uint64_t Start, uint64_t Length, FEXCore::Context::CodeRangeInvalidationFn callback) {
|
|
std::lock_guard lk(ThreadCreationMutex);
|
|
|
|
// Potential deferred since Thread might not be valid.
|
|
// Thread object isn't valid very early in frontend's initialization.
|
|
// To be more optimal the frontend should provide this code with a valid Thread object earlier.
|
|
auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread);
|
|
|
|
for (auto &Thread : Threads) {
|
|
CTX->InvalidateGuestCodeRange(Thread, Start, Length, callback);
|
|
}
|
|
}
|
|
|
|
fextl::vector<FEXCore::Core::InternalThreadState *> const *GetThreads() const {
|
|
return &Threads;
|
|
}
|
|
|
|
private:
|
|
FEXCore::Context::Context *CTX;
|
|
FEX::HLE::SignalDelegator *SignalDelegation;
|
|
|
|
FEXCore::ForkableUniqueMutex ThreadCreationMutex;
|
|
fextl::vector<FEXCore::Core::InternalThreadState *> Threads;
|
|
|
|
// Thread idling support.
|
|
bool Running{};
|
|
std::mutex IdleWaitMutex;
|
|
std::condition_variable IdleWaitCV;
|
|
std::atomic<uint32_t> IdleWaitRefCount{};
|
|
|
|
void HandleThreadDeletion(FEXCore::Core::InternalThreadState *Thread);
|
|
void NotifyPause();
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
SyscallFunctionDefinition const *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;
|
|
FEXCore::CodeLoader *GetCodeLoader() const override { return LocalLoader; }
|
|
void SetCodeLoader(FEXCore::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; }
|
|
|
|
protected:
|
|
SyscallHandler(FEXCore::Context::Context *_CTX, FEX::HLE::SignalDelegator *_SignalDelegation);
|
|
|
|
fextl::vector<SyscallFunctionDefinition> 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;
|
|
FEXCore::CodeLoader *LocalLoader{};
|
|
bool NeedToCheckXID{true};
|
|
|
|
#ifdef DEBUG_STRACE
|
|
void Strace(FEXCore::HLE::SyscallArguments *Args, uint64_t Ret);
|
|
#endif
|
|
|
|
fextl::unique_ptr<FEX::HLE::MemAllocator> Alloc32Handler{};
|
|
|
|
fextl::unique_ptr<FEXCore::HLE::SourcecodeMap> 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<MRID, MappedResource>;
|
|
|
|
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<uint64_t, VMAEntry> 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<typename T>
|
|
struct ArgToFmtString;
|
|
|
|
#define ARG_TO_STR(tpy, str) template<> struct FEX::HLE::ArgToFmtString<tpy> { 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<typename T>
|
|
struct ArgToFmtString<T*> {
|
|
inline static const char* const Format = "%p";
|
|
};
|
|
|
|
// Use ArgToFmtString and variadic template to create a format string from an args list
|
|
template<typename ...Args>
|
|
fextl::string CollectArgsFmtString() {
|
|
std::array<const char*, sizeof...(Args)> array = { ArgToFmtString<Args>::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<uint64_t>(Result) >= MAX_ERRNO;
|
|
}
|
|
|
|
[[maybe_unused]]
|
|
static bool HasSyscallError(const void* Result) {
|
|
return HasSyscallError(reinterpret_cast<uintptr_t>(Result));
|
|
}
|
|
|
|
template<bool IncrementOffset, typename T>
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
// 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)
|