#include "Common/MathUtils.h" #include "Interface/Context/Context.h" #include "Interface/Core/InternalThreadState.h" #include "Interface/HLE/Syscalls.h" #include "LogManager.h" #include #include constexpr uint64_t PAGE_SIZE = 4096; namespace FEXCore { void SyscallHandler::DefaultProgramBreak(FEXCore::Core::InternalThreadState *Thread, uint64_t Addr) { DataSpaceSize = 0; DataSpace = Addr; DefaultProgramBreakAddress = Addr; // Just allocate 1GB of data memory past the default program break location at this point CTX->MapRegion(Thread, Addr, 0x1000'0000); } uint64_t SyscallHandler::HandleSyscall(FEXCore::Core::InternalThreadState *Thread, FEXCore::HLE::SyscallArguments *Args) { uint64_t Result = 0; LogMan::Msg::D("Syscall: %d", Args->Argument[0]); switch (Args->Argument[0]) { case SYSCALL_UNAME: { struct _utsname { char sysname[65]; char nodename[65]; char release[65]; char version[65]; char machine[65]; }; _utsname *Local = CTX->MemoryMapper.GetPointer<_utsname*>(Args->Argument[1]); strcpy(Local->sysname, "Linux"); strcpy(Local->nodename, "FEXCore"); strcpy(Local->release, "5.0.0"); strcpy(Local->version, "#1"); strcpy(Local->machine, "x86_64"); Result = 0; break; } // Memory management case SYSCALL_BRK: { LogMan::Msg::D("\tBRK: 0x%lx - 0x%lx", Args->Argument[1], DataSpace); if (Args->Argument[1] == 0) { // Just wants to get the location of the program break atm Result = DataSpace + DataSpaceSize; } else { // Allocating out data space uint64_t NewEnd = Args->Argument[1]; if (NewEnd < DataSpace) { // Not allowed to move brk end below original start // Set the size to zero DataSpaceSize = 0; } else { uint64_t NewSize = NewEnd - DataSpace; DataSpaceSize = NewSize; } Result = DataSpace + DataSpaceSize; } break; } case SYSCALL_MMAP: { LogMan::Msg::D("\tMMAP( %p, 0x%lx, %d, 0x%x, %d, 0x%lx)", Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], Args->Argument[6]); int Flags = Args->Argument[4]; int GuestFD = Args->Argument[5]; int HostFD = -1; if (GuestFD != -1) { HostFD = FM.FindHostFD(GuestFD); } uint64_t Base = AlignDown(LastMMAP, PAGE_SIZE); uint64_t Size = AlignUp(Args->Argument[2], PAGE_SIZE); uint64_t FileSizeToUse = Args->Argument[2]; uint64_t Prot = Args->Argument[3]; #ifdef DEBUG_MMAP FileSizeToUse = Size; Prot = PROT_READ | PROT_WRITE | PROT_EXEC; #endif if (Flags & MAP_FIXED) { Base = Args->Argument[1]; void *HostPtr = CTX->MemoryMapper.GetPointer(Base); if (!HostPtr) { HostPtr = CTX->MapRegion(Thread, Base, Size, true); } else { LogMan::Msg::D("\tMapping Fixed pointer in already mapped space: 0x%lx -> %p", Base, HostPtr); } if (HostFD != -1) { #ifdef DEBUG_MMAP // We are a file. Screw you I'm going to just memcpy you in to place void *FileMem = mmap(nullptr, FileSizeToUse, Prot, MAP_PRIVATE, HostFD, Args->Argument[6]); if (FileMem == MAP_FAILED) { LogMan::Msg::A("Couldn't map file to %p\n", HostPtr); } memcpy(HostPtr, FileMem, FileSizeToUse); munmap(FileMem, Size); #else void *FileMem = mmap(HostPtr, FileSizeToUse, Prot, MAP_PRIVATE | MAP_FIXED, HostFD, Args->Argument[6]); if (FileMem == MAP_FAILED) { LogMan::Msg::A("Couldn't map file to %p\n", HostPtr); } #endif } else { LogMan::Throw::A(Args->Argument[6] == 0, "Don't understand a fixed offset mmap"); } Result = Base; } else { // XXX: MMAP should map memory regions for all threads void *HostPtr = CTX->MapRegion(Thread, Base, Size, true); if (HostFD != -1) { #ifdef DEBUG_MMAP // We are a file. Screw you I'm going to just memcpy you in to place void *FileMem = mmap(nullptr, FileSizeToUse, Prot, MAP_PRIVATE, HostFD, Args->Argument[6]); if (FileMem == MAP_FAILED) { LogMan::Msg::A("Couldn't map file to %p\n", HostPtr); } memcpy(HostPtr, FileMem, FileSizeToUse); munmap(FileMem, Size); #else void *FileMem = mmap(HostPtr, FileSizeToUse, Prot, MAP_PRIVATE | MAP_FIXED, HostFD, Args->Argument[6]); if (FileMem == MAP_FAILED) { LogMan::Msg::A("Couldn't map file to %p\n", HostPtr); } #endif } LastMMAP += Size; Result = Base; } break; } case SYSCALL_MPROTECT: { LogMan::Msg::D("\tMPROTECT: 0x%x, 0x%lx, 0x%lx", Args->Argument[1], Args->Argument[2], Args->Argument[3]); void *HostPtr = CTX->MemoryMapper.GetPointer(Args->Argument[1]); Result = mprotect(HostPtr, Args->Argument[2], Args->Argument[3]); break; } case SYSCALL_ARCH_PRCTL: { LogMan::Msg::D("\tPRTCL: 0x%x: 0x%lx", Args->Argument[1], Args->Argument[2]); switch (Args->Argument[1]) { case 0x1001: // ARCH_SET_GS Thread->State.State.gs = Args->Argument[2]; break; case 0x1002: // ARCH_SET_FS Thread->State.State.fs = Args->Argument[2]; break; case 0x1003: // ARCH_GET_FS *CTX->MemoryMapper.GetPointer(Args->Argument[2]) = Thread->State.State.fs; break; case 0x1004: // ARCH_GET_GS *CTX->MemoryMapper.GetPointer(Args->Argument[2]) = Thread->State.State.gs; break; default: LogMan::Msg::E("Unknown prctl: 0x%x", Args->Argument[1]); CTX->ShouldStop = true; break; } Result = 0; break; } // Thread management case SYSCALL_GETUID: Result = Thread->State.ThreadManager.GetUID(); break; case SYSCALL_GETGID: Result = Thread->State.ThreadManager.GetGID(); break; case SYSCALL_GETEUID: Result = Thread->State.ThreadManager.GetEUID(); break; case SYSCALL_GETEGID: Result = Thread->State.ThreadManager.GetEGID(); break; case SYSCALL_GETTID: Result = Thread->State.ThreadManager.GetTID(); break; case SYSCALL_GETPID: Result = Thread->State.ThreadManager.GetPID(); break; case SYSCALL_EXIT: LogMan::Msg::D("Thread exit with: %zd\n", Args->Argument[1]); Thread->State.RunningEvents.ShouldStop = true; break; case SYSCALL_WAIT4: LogMan::Msg::I("wait4(%lx,\n\t%lx,\n\t%lx,\n\t%lx)", Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4]); break; // Futexes case SYSCALL_FUTEX: { // 0 : uaddr // 1 : op // 2: val // 3: utime // 4: uaddr2 // 5: val3 LogMan::Msg::I("futex(%lx,\n\t%lx,\n\t%lx,\n\t%lx,\n\t%lx,\n\t%lx)", Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], Args->Argument[6]); uint8_t Command = Args->Argument[2] & 0xF; Result = 0; switch (Command) { case 0: { // WAIT LogMan::Throw::A(!Args->Argument[4], "Can't handle timed futexes"); Futex *futex = new Futex{}; // XXX: Definitely a memory leak. When should we free this? futex->Addr = CTX->MemoryMapper.GetPointer*>(Args->Argument[1]); futex->Val = Args->Argument[3]; EmplaceFutex(Args->Argument[1], futex); { std::unique_lock lk(futex->Mutex); futex->cv.wait(lk, [futex] { return futex->Addr->load() != futex->Val; }); } break; } case 1: { // WAKE Futex *futex = GetFutex(Args->Argument[1]); if (!futex) { Result = 0; break; } for (uint64_t i = 0; i < Args->Argument[3]; ++i) futex->cv.notify_one(); break; } default: LogMan::Msg::A("Unknown futex command"); break; } break; } case SYSCALL_CLONE: { // 0: clone_flags // 1: New SP // 2: parent tidptr // 3: child tidptr // 4: TLS LogMan::Msg::I("clone(%lx,\n\t%lx,\n\t%lx,\n\t%lx,\n\t%lx,\n\t%lx,\n\t%lx)", Args->Argument[0], Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5]); uint32_t Flags = Args->Argument[1]; uint64_t NewSP = Args->Argument[2]; uint64_t ParentTID = Args->Argument[3]; uint64_t ChildTID = Args->Argument[4]; uint64_t NewTLS = Args->Argument[5]; #define FLAGPRINT(x, y) if (Flags & (y)) LogMan::Msg::I("\tFlag: " #x) FLAGPRINT(CSIGNAL, 0x000000FF); FLAGPRINT(CLONE_VM, 0x00000100); FLAGPRINT(CLONE_FS, 0x00000200); FLAGPRINT(CLONE_FILES, 0x00000400); FLAGPRINT(CLONE_SIGHAND, 0x00000800); FLAGPRINT(CLONE_PTRACE, 0x00002000); FLAGPRINT(CLONE_VFORK, 0x00004000); FLAGPRINT(CLONE_PARENT, 0x00008000); FLAGPRINT(CLONE_THREAD, 0x00010000); FLAGPRINT(CLONE_NEWNS, 0x00020000); FLAGPRINT(CLONE_SYSVSEM, 0x00040000); FLAGPRINT(CLONE_SETTLS, 0x00080000); FLAGPRINT(CLONE_PARENT_SETTID, 0x00100000); FLAGPRINT(CLONE_CHILD_CLEARTID, 0x00200000); FLAGPRINT(CLONE_DETACHED, 0x00400000); FLAGPRINT(CLONE_UNTRACED, 0x00800000); FLAGPRINT(CLONE_CHILD_SETTID, 0x01000000); FLAGPRINT(CLONE_NEWCGROUP, 0x02000000); FLAGPRINT(CLONE_NEWUTS, 0x04000000); FLAGPRINT(CLONE_NEWIPC, 0x08000000); FLAGPRINT(CLONE_NEWUSER, 0x10000000); FLAGPRINT(CLONE_NEWPID, 0x20000000); FLAGPRINT(CLONE_NEWNET, 0x40000000); FLAGPRINT(CLONE_IO, 0x80000000); FEXCore::Core::CPUState NewThreadState{}; // Clone copies the parent thread's state memcpy(&NewThreadState, &Thread->State.State, sizeof(FEXCore::Core::CPUState)); NewThreadState.gregs[FEXCore::X86State::REG_RAX] = 0; NewThreadState.gregs[FEXCore::X86State::REG_RBX] = 0; NewThreadState.gregs[FEXCore::X86State::REG_RBP] = 0; NewThreadState.gregs[FEXCore::X86State::REG_RSP] = NewSP; NewThreadState.fs = NewTLS; // Set us to start just after the syscall instruction NewThreadState.rip += 2; auto NewThread = CTX->CreateThread(&NewThreadState, ParentTID, ChildTID); CTX->CopyMemoryMapping(Thread, NewThread); // Sets the child TID to pointer in ParentTID if (Flags & CLONE_PARENT_SETTID) { uint64_t *TIDPtr = CTX->MemoryMapper.GetPointer(ParentTID); TIDPtr[0] = NewThread->State.ThreadManager.GetTID(); } // Sets the child TID to the pointer in ChildTID if (Flags & CLONE_CHILD_SETTID) { uint64_t *TIDPtr = CTX->MemoryMapper.GetPointer(ChildTID); TIDPtr[0] = NewThread->State.ThreadManager.GetTID(); } // When the thread exits, clear the child thread ID at ChildTID // Additionally wakeup a futex at that address // Address /may/ be changed with SET_TID_ADDRESS syscall if (Flags & CLONE_CHILD_CLEARTID) { } CTX->InitializeThread(NewThread); // Actually start the thread CTX->RunThread(NewThread); // Return the new threads TID Result = NewThread->State.ThreadManager.GetTID(); break; } // File management case SYSCALL_READ: Result = FM.Read(Args->Argument[1], CTX->MemoryMapper.GetPointer(Args->Argument[2]), Args->Argument[3]); break; case SYSCALL_WRITE: Result = FM.Write(Args->Argument[1], CTX->MemoryMapper.GetPointer(Args->Argument[2]), Args->Argument[3]); break; case SYSCALL_OPEN: Result = FM.Open(CTX->MemoryMapper.GetPointer(Args->Argument[1]), Args->Argument[2], Args->Argument[3]); break; case SYSCALL_CLOSE: Result = FM.Close(Args->Argument[1]); break; case SYSCALL_STAT: Result = FM.Stat(CTX->MemoryMapper.GetPointer(Args->Argument[1]), CTX->MemoryMapper.GetPointer(Args->Argument[2])); break; case SYSCALL_FSTAT: Result = FM.Fstat(Args->Argument[1], CTX->MemoryMapper.GetPointer(Args->Argument[2])); break; case SYSCALL_LSEEK: Result = FM.Lseek(Args->Argument[1], Args->Argument[2], Args->Argument[3]); break; case SYSCALL_WRITEV: Result = FM.Writev(Args->Argument[1], CTX->MemoryMapper.GetPointer(Args->Argument[2]), Args->Argument[3]); break; case SYSCALL_ACCESS: Result = FM.Access( CTX->MemoryMapper.GetPointer(Args->Argument[1]), Args->Argument[2]); break; case SYSCALL_READLINK: Result = FM.Readlink( CTX->MemoryMapper.GetPointer(Args->Argument[1]), CTX->MemoryMapper.GetPointer(Args->Argument[2]), Args->Argument[3]); break; case SYSCALL_OPENAT: Result = FM.Openat( Args->Argument[1], CTX->MemoryMapper.GetPointer(Args->Argument[2]), Args->Argument[3], Args->Argument[4]); break; case SYSCALL_CLOCK_GETTIME: { timespec *ClockResult = CTX->MemoryMapper.GetPointer(Args->Argument[2]); Result = clock_gettime(Args->Argument[1], ClockResult); // XXX: Debug // memset(ClockResult, 0, sizeof(timespec)); } break; case SYSCALL_NANOSLEEP: { timespec const* req = CTX->MemoryMapper.GetPointer(Args->Argument[1]); timespec *rem = CTX->MemoryMapper.GetPointer(Args->Argument[2]); Result = nanosleep(req, rem); break; } case SYSCALL_SET_TID_ADDRESS: { Thread->State.ThreadManager.child_tid = Args->Argument[1]; Result = Thread->State.ThreadManager.GetTID(); break; } case SYSCALL_SET_ROBUST_LIST: { Thread->State.ThreadManager.robust_list_head = Args->Argument[1]; Result = 0; break; } case SYSCALL_PRLIMIT64: { LogMan::Throw::A(Args->Argument[3] == 0, "Guest trying to set limit for %d", Args->Argument[2]); struct rlimit { uint64_t rlim_cur; uint64_t rlim_max; }; switch (Args->Argument[2]) { case 3: { // Stack limits rlimit *old_limit = CTX->MemoryMapper.GetPointer(Args->Argument[3]); // Default size old_limit->rlim_cur = 8 * 1024; old_limit->rlim_max = ~0ULL; break; } default: LogMan::Msg::A("Unknown PRLimit: %d", Args->Argument[2]); } Result = 0; break; } // Currently unhandled // Return fake result case SYSCALL_RT_SIGACTION: case SYSCALL_RT_SIGPROCMASK: case SYSCALL_EXIT_GROUP: case SYSCALL_TGKILL: case SYSCALL_MUNMAP: Result = 0; break; default: Result = -1; LogMan::Msg::A("Unknown syscall: %d", Args->Argument[0]); break; } return Result; } }