/* $info$ category: LinuxSyscalls ~ Linux syscall emulation, marshaling and passthrough tags: LinuxSyscalls|common desc: Glue logic, brk allocations $end_info$ */ #include "Linux/Utils/ELFContainer.h" #include "Linux/Utils/ELFParser.h" #include "Tests/LinuxSyscalls/LinuxAllocator.h" #include "Tests/LinuxSyscalls/Syscalls.h" #include "Tests/LinuxSyscalls/Syscalls/Thread.h" #include "Tests/LinuxSyscalls/x32/Syscalls.h" #include "Tests/LinuxSyscalls/x64/Syscalls.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace FEXCore::Context { struct Context; } namespace FEX::HLE { class SignalDelegator; SyscallHandler *_SyscallHandler{}; static bool IsSupportedByInterpreter(std::string const &Filename) { // If it is a supported ELF then we can if (ELFLoader::ELFContainer::IsSupportedELF(Filename.c_str())) { return true; } // If it is a shebang then we also can std::fstream File; size_t FileSize{0}; File.open(Filename, std::fstream::in | std::fstream::binary); if (!File.is_open()) return false; File.seekg(0, File.end); FileSize = File.tellg(); File.seekg(0, File.beg); // Is the file large enough for shebang if (FileSize <= 2) return false; // Handle shebang files if (File.get() == '#' && File.get() == '!') { std::string InterpreterLine; std::getline(File, InterpreterLine); std::vector ShebangArguments{}; // Shebang line can have a single argument std::istringstream InterpreterSS(InterpreterLine); std::string Argument; while (std::getline(InterpreterSS, Argument, ' ')) { if (Argument.empty()) { continue; } ShebangArguments.emplace_back(Argument); } // Executable argument std::string &ShebangProgram = ShebangArguments[0]; // If the filename is absolute then prepend the rootfs // If it is relative then don't append the rootfs if (ShebangProgram[0] == '/') { std::string RootFS = FEX::HLE::_SyscallHandler->RootFSPath(); ShebangProgram = RootFS + ShebangProgram; } std::error_code ec; bool exists = std::filesystem::exists(ShebangProgram, ec); if (ec || !exists) { return false; } return true; } return false; } uint64_t ExecveHandler(const char *pathname, char* const* argv, char* const* envp, ExecveAtArgs *Args) { std::string Filename{}; std::error_code ec; std::string RootFS = FEX::HLE::_SyscallHandler->RootFSPath(); // Check the rootfs if it is available first if (pathname[0] == '/') { auto Path = FEX::HLE::_SyscallHandler->FM.GetEmulatedPath(pathname, true); if (!Path.empty() && std::filesystem::exists(Path, ec)) { Filename = Path; } else { Filename = pathname; } } else { Filename = pathname; } bool exists = std::filesystem::exists(Filename, ec); if (ec || !exists) { return -ENOENT; } int pid = getpid(); char PidSelfPath[50]; snprintf(PidSelfPath, 50, "/proc/%i/exe", pid); if (strcmp(pathname, "/proc/self/exe") == 0 || strcmp(pathname, "/proc/thread-self/exe") == 0 || strcmp(pathname, PidSelfPath) == 0) { // If pointing to self then redirect to the application // JRE and shapez.io does this Filename = FEX::HLE::_SyscallHandler->Filename(); } // If we don't have the interpreter installed we need to be extra careful for ENOEXEC // Reasoning is that if we try executing a file from FEXLoader then this process loses the ENOEXEC flag // Kernel does its own checks for file format support for this // We can only call execve directly if we both have an interpreter installed AND were ran with the interpreter // If the user ran FEX through FEXLoader then we must go down the emulated path ELFLoader::ELFContainer::ELFType Type = ELFLoader::ELFContainer::GetELFType(Filename); uint64_t Result{}; if (FEX::HLE::_SyscallHandler->IsInterpreterInstalled() && FEX::HLE::_SyscallHandler->IsInterpreter() && (Type == ELFLoader::ELFContainer::ELFType::TYPE_X86_32 || Type == ELFLoader::ELFContainer::ELFType::TYPE_X86_64)) { // If the FEX interpreter is installed then just execve the ELF file // This will stay inside of our emulated environment since binfmt_misc will capture it if (Args) { Result = ::syscall(SYS_execveat, Args->dirfd, Filename.c_str(), argv, envp, Args->flags); } else { Result = execve(Filename.c_str(), argv, envp); } SYSCALL_ERRNO(); } if (!IsSupportedByInterpreter(Filename) && Type == ELFLoader::ELFContainer::ELFType::TYPE_NONE) { // If our interpeter doesn't support this file format AND ELF format is NONE then ENOEXEC // binfmt_misc could end up handling this case but we can't know that without parsing binfmt_misc ourselves // Return -ENOEXEC until proven otherwise return -ENOEXEC; } if (Type == ELFLoader::ELFContainer::ELFType::TYPE_OTHER_ELF) { // We are trying to execute an ELF of a different architecture // We can't know if we can support this without architecture specific checks and binfmt_misc parsing // Just execve it and let the kernel handle the process if (Args) { Result = ::syscall(SYS_execveat, Args->dirfd, Filename.c_str(), argv, envp, Args->flags); } else { Result = execve(Filename.c_str(), argv, envp); } SYSCALL_ERRNO(); } // We don't have an interpreter installed or we are executing a non-ELF executable // We now need to munge the arguments std::vector ExecveArgs{}; FEX::HLE::_SyscallHandler->GetCodeLoader()->GetExecveArguments(&ExecveArgs); if (!FEX::HLE::_SyscallHandler->IsInterpreter()) { // If we were launched from FEXLoader then we need to make sure to split arguments from FEXLoader and guest ExecveArgs.emplace_back("--"); } if (argv) { // Overwrite the filename with the new one we are redirecting to ExecveArgs.emplace_back(Filename.c_str()); auto OldArgv = argv; // Skip filename argument ++OldArgv; while (*OldArgv) { // Append the arguments together ExecveArgs.emplace_back(*OldArgv); ++OldArgv; } // Emplace nullptr at the end to stop ExecveArgs.emplace_back(nullptr); } if (Args) { Result = ::syscall(SYS_execveat, Args->dirfd, "/proc/self/exe", const_cast(ExecveArgs.data()), envp, Args->flags); } else { Result = execve("/proc/self/exe", const_cast(ExecveArgs.data()), envp); } SYSCALL_ERRNO(); } static bool AnyFlagsSet(uint64_t Flags, uint64_t Mask) { return (Flags & Mask) != 0; } static bool AllFlagsSet(uint64_t Flags, uint64_t Mask) { return (Flags & Mask) == Mask; } struct StackFrameData { FEXCore::Core::InternalThreadState *Thread{}; FEXCore::Context::Context *CTX{}; FEXCore::Core::CpuStateFrame NewFrame{}; FEX::HLE::clone3_args GuestArgs{}; void *NewStack; size_t StackSize; }; struct StackFramePlusRet { uint64_t Ret; StackFrameData Data; uint64_t Pad; }; [[noreturn]] static void Clone3HandlerRet() { StackFrameData *Data = (StackFrameData*)alloca(0); uint64_t Result = FEX::HLE::HandleNewClone(Data->Thread, Data->CTX, &Data->NewFrame, &Data->GuestArgs); FEXCore::Threads::DeallocateStackObject(Data->NewStack, Data->StackSize); // To behave like a real clone, we now just need to call exit here exit(Result); FEX_UNREACHABLE; } static int Clone2HandlerRet(void *arg) { StackFrameData *Data = (StackFrameData*)arg; uint64_t Result = FEX::HLE::HandleNewClone(Data->Thread, Data->CTX, &Data->NewFrame, &Data->GuestArgs); FEXCore::Threads::DeallocateStackObject(Data->NewStack, Data->StackSize); FEXCore::Allocator::free(arg); return Result; } // Clone3 flags #ifndef CLONE_CLEAR_SIGHAND #define CLONE_CLEAR_SIGHAND 0x100000000ULL #endif #ifndef CLONE_INTO_CGROUP #define CLONE_INTO_CGROUP 0x200000000ULL #endif #ifndef CLONE_NEWTIME // Overlaps CSIGNAL, can only be used with clone3 and not clone2 #define CLONE_NEWTIME 0x00000080ULL #endif static void PrintFlags(uint64_t Flags){ #define FLAGPRINT(x, y) if (Flags & (y)) LogMan::Msg::IFmt("\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); FLAGPRINT(CLONE_PIDFD, 0x00001000); #undef FLAGPRINT }; static uint64_t Clone2Handler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args) { StackFrameData *Data = (StackFrameData *)FEXCore::Allocator::malloc(sizeof(StackFrameData)); Data->Thread = Frame->Thread; Data->CTX = Frame->Thread->CTX; Data->GuestArgs = *args; // In the case of thread, we need a new stack Data->StackSize = 8 * 1024 * 1024; Data->NewStack = FEXCore::Threads::AllocateStackObject(Data->StackSize); // Create a copy of the parent frame memcpy(&Data->NewFrame, Frame, sizeof(FEXCore::Core::CpuStateFrame)); // Remove flags that will break us constexpr uint64_t INVALID_FOR_HOST = CLONE_SETTLS; uint64_t Flags = args->args.flags & ~INVALID_FOR_HOST; uint64_t Result = ::clone( Clone2HandlerRet, // To be called function (void*)((uint64_t)Data->NewStack + Data->StackSize), // Stack Flags, //Flags Data, //Argument (pid_t*)args->args.parent_tid, // parent_tid 0, // XXX: What is correct for this? tls (pid_t*)args->args.child_tid); // child_tid // Only parent will get here SYSCALL_ERRNO(); } static uint64_t Clone3Handler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args) { // In the case of thread, we need a new stack uint64_t StackSize = 8 * 1024 * 1024; void *NewStack = FEXCore::Threads::AllocateStackObject(StackSize); constexpr size_t Offset = sizeof(StackFramePlusRet); StackFramePlusRet *Data = (StackFramePlusRet*)(reinterpret_cast(NewStack) + StackSize - Offset); Data->Ret = (uint64_t)Clone3HandlerRet; Data->Data.Thread = Frame->Thread; Data->Data.CTX = Frame->Thread->CTX; Data->Data.GuestArgs = *args; Data->Data.StackSize = StackSize; Data->Data.NewStack = NewStack; FEX::HLE::kernel_clone3_args HostArgs{}; HostArgs.flags = args->args.flags; HostArgs.pidfd = args->args.pidfd; HostArgs.child_tid = args->args.child_tid; HostArgs.parent_tid = args->args.parent_tid; HostArgs.exit_signal = args->args.exit_signal; // Host stack is always created HostArgs.stack = reinterpret_cast(NewStack); HostArgs.stack_size = StackSize - Offset; // Needs to be 16 byte aligned HostArgs.tls = 0; // XXX: What is correct for this? HostArgs.set_tid = args->args.set_tid; HostArgs.set_tid_size= args->args.set_tid_size; HostArgs.cgroup = args->args.cgroup; // Create a copy of the parent frame memcpy(&Data->Data.NewFrame, Frame, sizeof(FEXCore::Core::CpuStateFrame)); uint64_t Result = ::syscall(SYSCALL_DEF(clone3), &HostArgs, sizeof(HostArgs)); // Only parent will get here SYSCALL_ERRNO(); }; uint64_t CloneHandler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args) { uint64_t flags = args->args.flags; auto HasUnhandledFlags = [](FEX::HLE::clone3_args *args) -> bool { constexpr uint64_t UNHANDLED_FLAGS = CLONE_NEWNS | // CLONE_UNTRACED | CLONE_NEWCGROUP | CLONE_NEWUTS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNET | CLONE_IO | CLONE_CLEAR_SIGHAND | CLONE_INTO_CGROUP; if ((args->args.flags & UNHANDLED_FLAGS) != 0) { // Basic unhandled flags return true; } if (args->args.set_tid_size > 0) { // set_tid isn't exposed through anything other than clone3 return true; } if (args->Type == TypeOfClone::TYPE_CLONE3) { if (AnyFlagsSet(args->args.flags, CLONE_NEWTIME)) { // New time namespace overlaps with CSIGNAL, only available in clone3 return true; } } if (AnyFlagsSet(args->args.flags, CLONE_THREAD)) { if (!AllFlagsSet(args->args.flags, CLONE_SYSVSEM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)) { LogMan::Msg::IFmt("clone: CLONE_THREAD: Unsuported flags w/ CLONE_THREAD (Shared Resources), {:X}", args->args.flags); return false; } } else { if (AnyFlagsSet(args->args.flags, CLONE_SYSVSEM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM)) { // CLONE_VM is particularly nasty here // Memory regions at the point of clone(More similar to a fork) are shared LogMan::Msg::IFmt("clone: Unsuported flags w/o CLONE_THREAD (Shared Resources), {:X}", args->args.flags); return false; } } // We support everything here return false; }; if (flags & CLONE_VM) { MarkMemoryShared(Frame->Thread->CTX); } // If there are flags that can't be handled regularly then we need to hand off to the true clone handler if (HasUnhandledFlags(args)) { if (!AnyFlagsSet(flags, CLONE_THREAD)) { // Has an unsupported flag // Fall to a handler that can handle this case if (args->Type == TYPE_CLONE2) { return Clone2Handler(Frame, args); } else { return Clone3Handler(Frame, args); } } else { LogMan::Msg::IFmt("Unsupported flag with CLONE_THREAD. This breaks TLS, falling down classic thread path"); PrintFlags(flags); } } constexpr uint64_t TASK_MAX = (1ULL << 48); // 48-bits until we can query the host side VA sanely. AArch64 doesn't expose this in cpuinfo if (args->args.tls && args->args.tls >= TASK_MAX) { return -EPERM; } auto Thread = Frame->Thread; if (AnyFlagsSet(flags, CLONE_PTRACE)) { PrintFlags(flags); LogMan::Msg::DFmt("clone: Ptrace* not supported"); } if (!(flags & CLONE_THREAD)) { if (flags & CLONE_VFORK) { PrintFlags(flags); flags &= ~CLONE_VM; LogMan::Msg::DFmt("clone: WARNING: CLONE_VFORK w/o CLONE_THREAD"); } // CLONE_PARENT is ignored (Implied by CLONE_THREAD) return FEX::HLE::ForkGuest(Thread, Frame, flags, reinterpret_cast(args->args.stack), args->args.stack_size, reinterpret_cast(args->args.parent_tid), reinterpret_cast(args->args.child_tid), reinterpret_cast(args->args.tls)); } else { auto NewThread = FEX::HLE::CreateNewThread(Thread->CTX, Frame, args); // Return the new threads TID uint64_t Result = NewThread->ThreadManager.GetTID(); if (flags & CLONE_VFORK) { NewThread->DestroyedByParent = true; } // Actually start the thread FEXCore::Context::RunThread(Thread->CTX, NewThread); if (flags & CLONE_VFORK) { // If VFORK is set then the calling process is suspended until the thread exits with execve or exit NewThread->ExecutionThread->join(nullptr); // Normally a thread cleans itself up on exit. But because we need to join, we are now responsible FEXCore::Context::DestroyThread(Thread->CTX, NewThread); } SYSCALL_ERRNO(); } }; uint64_t SyscallHandler::HandleBRK(FEXCore::Core::CpuStateFrame *Frame, void *Addr) { std::lock_guard lk(MMapMutex); uint64_t Result; if (Addr == nullptr) { // Just wants to get the location of the program break atm Result = DataSpace + DataSpaceSize; } else { // Allocating out data space uint64_t NewEnd = reinterpret_cast(Addr); 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; uint64_t NewSizeAligned = FEXCore::AlignUp(NewSize, 4096); if (NewSizeAligned < DataSpaceMaxSize) { // If we are shrinking the brk then munmap the ranges // That way we gain the memory back and also give the application zero pages if it allocates again // DataspaceMaxSize is always page aligned uint64_t RemainingSize = DataSpaceMaxSize - NewSizeAligned; // We have pages we can unmap auto ok = GuestMunmap(reinterpret_cast(DataSpace + NewSizeAligned), RemainingSize); LOGMAN_THROW_A_FMT(ok != -1, "Munmap failed"); DataSpaceMaxSize = NewSizeAligned; } else if (NewSize > DataSpaceMaxSize) { constexpr static uint64_t SizeAlignment = 8 * 1024 * 1024; uint64_t AllocateNewSize = FEXCore::AlignUp(NewSize, SizeAlignment) - DataSpaceMaxSize; if (!Is64BitMode() && (DataSpace + DataSpaceMaxSize + AllocateNewSize > 0x1'0000'0000ULL)) { // If we are 32bit and we tried going about the 32bit limit then out of memory return DataSpace + DataSpaceSize; } uint64_t NewBRK{}; NewBRK = (uint64_t)GuestMmap((void*)(DataSpace + DataSpaceMaxSize), AllocateNewSize, PROT_READ | PROT_WRITE, MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (NewBRK != ~0ULL && NewBRK != (DataSpace + DataSpaceMaxSize)) { // Couldn't allocate that the region we wanted // Can happen if MAP_FIXED_NOREPLACE isn't understood by the kernel int ok = GuestMunmap(reinterpret_cast(NewBRK), AllocateNewSize); LOGMAN_THROW_A_FMT(ok != -1, "Munmap failed"); NewBRK = ~0ULL; } if (NewBRK == ~0ULL) { // If we couldn't allocate a new region then out of memory return DataSpace + DataSpaceSize; } else { // Increase our BRK size DataSpaceMaxSize += AllocateNewSize; } } DataSpaceSize = NewSize; } Result = DataSpace + DataSpaceSize; } return Result; } void SyscallHandler::DefaultProgramBreak(uint64_t Base, uint64_t Size) { DataSpace = Base; DataSpaceMaxSize = Size; DataSpaceStartingSize = Size; } SyscallHandler::SyscallHandler(FEXCore::Context::Context *_CTX, FEX::HLE::SignalDelegator *_SignalDelegation) : FM {_CTX} , CTX {_CTX} , SignalDelegation {_SignalDelegation} { FEX::HLE::_SyscallHandler = this; HostKernelVersion = CalculateHostKernelVersion(); GuestKernelVersion = CalculateGuestKernelVersion(); Alloc32Handler = FEX::HLE::Create32BitAllocator(); if (SMCChecks == FEXCore::Config::CONFIG_SMC_MTRACK) { SignalDelegation->RegisterHostSignalHandler(SIGSEGV, HandleSegfault, true); } } SyscallHandler::~SyscallHandler() { FEXCore::Allocator::munmap(reinterpret_cast(DataSpace), DataSpaceMaxSize); } uint32_t SyscallHandler::CalculateHostKernelVersion() { struct utsname buf{}; if (uname(&buf) == -1) { return 0; } int32_t Major{}; int32_t Minor{}; int32_t Patch{}; char Tmp{}; std::istringstream ss{buf.release}; ss >> Major; ss.read(&Tmp, 1); ss >> Minor; ss.read(&Tmp, 1); ss >> Patch; return (Major << 24) | (Minor << 16) | Patch; } uint32_t SyscallHandler::CalculateGuestKernelVersion() { // We currently only emulate a kernel between the ranges of Kernel 5.0.0 and 5.18.0 return std::max(KernelVersion(5, 0), std::min(KernelVersion(5, 18), GetHostKernelVersion())); } uint64_t SyscallHandler::HandleSyscall(FEXCore::Core::CpuStateFrame *Frame, FEXCore::HLE::SyscallArguments *Args) { if (Args->Argument[0] >= Definitions.size()) { return -ENOSYS; } auto &Def = Definitions[Args->Argument[0]]; uint64_t Result{}; switch (Def.NumArgs) { case 0: Result = std::invoke(Def.Ptr0, Frame); break; case 1: Result = std::invoke(Def.Ptr1, Frame, Args->Argument[1]); break; case 2: Result = std::invoke(Def.Ptr2, Frame, Args->Argument[1], Args->Argument[2]); break; case 3: Result = std::invoke(Def.Ptr3, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3]); break; case 4: Result = std::invoke(Def.Ptr4, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4]); break; case 5: Result = std::invoke(Def.Ptr5, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5]); break; case 6: Result = std::invoke(Def.Ptr6, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], Args->Argument[6]); break; // for missing syscalls case 255: return std::invoke(Def.Ptr1, Frame, Args->Argument[0]); default: LOGMAN_MSG_A_FMT("Unhandled syscall: {}", Args->Argument[0]); return -1; break; } #ifdef DEBUG_STRACE Strace(Args, Result); #endif return Result; } #ifdef DEBUG_STRACE void SyscallHandler::Strace(FEXCore::HLE::SyscallArguments *Args, uint64_t Ret) { auto &Def = Definitions[Args->Argument[0]]; switch (Def.NumArgs) { case 0: LogMan::Msg::D(Def.StraceFmt.c_str(), Ret); break; case 1: LogMan::Msg::D(Def.StraceFmt.c_str(), Args->Argument[1], Ret); break; case 2: LogMan::Msg::D(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Ret); break; case 3: LogMan::Msg::D(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Ret); break; case 4: LogMan::Msg::D(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Ret); break; case 5: LogMan::Msg::D(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], Ret); break; case 6: LogMan::Msg::D(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], Args->Argument[6], Ret); break; default: break; } } #endif uint64_t UnimplementedSyscall(FEXCore::Core::CpuStateFrame *Frame, uint64_t SyscallNumber) { ERROR_AND_DIE_FMT("Unhandled system call: {}", SyscallNumber); return -ENOSYS; } uint64_t UnimplementedSyscallSafe(FEXCore::Core::CpuStateFrame *Frame, uint64_t SyscallNumber) { return -ENOSYS; } void SyscallHandler::LockBeforeFork() { FM.GetFDLock()->lock(); // XXX shared_mutex has issues with locking and forks // VMATracking.Mutex.lock(); // Add other mutexes here } void SyscallHandler::UnlockAfterFork() { // Add other mutexes here // XXX shared_mutex has issues with locking and forks // VMATracking.Mutex.unlock(); FM.GetFDLock()->unlock(); } static bool isHEX(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); } std::unique_ptr SyscallHandler::GenerateMap(const std::string_view& GuestBinaryFile, const std::string_view& GuestBinaryFileId) { ELFParser GuestELF; if (!GuestELF.ReadElf(std::string(GuestBinaryFile))) { LogMan::Msg::DFmt("GenerateMap: '{}' is not an elf file?", GuestBinaryFile); return {}; } struct stat GuestBinaryFileStat; if (stat(GuestBinaryFile.data(), &GuestBinaryFileStat)) { LogMan::Msg::DFmt("GenerateMap: failed to stat '{}'", GuestBinaryFile); return {}; } std::error_code ec; auto FexSrcPath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "fexsrc"; std::filesystem::create_directories(FexSrcPath, ec); if (ec) { LogMan::Msg::DFmt("GenerateMap: failed to create_directories '{}'", FexSrcPath.string()); return {}; } auto GuestSourceFile = (FexSrcPath / GuestBinaryFileId).string() + ".src"; struct stat GuestSourceFileStat; if (stat(GuestSourceFile.data(), &GuestSourceFileStat) != 0 || GuestBinaryFileStat.st_mtime > GuestSourceFileStat.st_mtime) { LogMan::Msg::DFmt("GenerateMap: Generating source for '{}'", GuestBinaryFile); auto command = fmt::format("x86_64-linux-gnu-objdump -SC \'{}\' > '{}'", GuestBinaryFile, GuestSourceFile); if (system(command.c_str()) != 0) { LogMan::Msg::DFmt("GenerateMap: '{}' failed", command); return {}; } } auto GuestIndexFile = (FexSrcPath / GuestBinaryFileId).string() + ".idx"; struct stat GuestIndexFileStat; bool GenerateIndex = stat(GuestIndexFile.data(), &GuestIndexFileStat) != 0 || GuestSourceFileStat.st_mtime > GuestIndexFileStat.st_mtime; if (!GenerateIndex) { // Index file de-serialization LogMan::Msg::DFmt("GenerateMap: Reading index '{}'", GuestIndexFile); std::ifstream Stream(GuestIndexFile); if (!Stream) { LogMan::Msg::DFmt("GenerateMap: Failed to open '{}'", GuestIndexFile); goto DoGenerate; } //"fexsrcindex0" char filemagic[12]; Stream.read(filemagic, sizeof(filemagic)); if (memcmp(filemagic, "fexsrcindex0", sizeof(filemagic)) != 0) { LogMan::Msg::DFmt("GenerateMap: '{}' has invalid magic '{}'", GuestIndexFile, filemagic); goto DoGenerate; } auto rv = std::make_unique(); { auto len = rv->SourceFile.size(); Stream.read((char*)&len, sizeof(len)); rv->SourceFile.resize(len); Stream.read(rv->SourceFile.data(), len); } { auto len = rv->SortedLineMappings.size(); Stream.read((char*)&len, sizeof(len)); rv->SortedLineMappings.resize(len); for (auto &Mapping: rv->SortedLineMappings) { Stream.read((char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin)); Stream.read((char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd)); Stream.read((char*)&Mapping.LineNumber, sizeof(Mapping.LineNumber)); } } { auto len = rv->SortedSymbolMappings.size(); Stream.read((char*)&len, sizeof(len)); rv->SortedSymbolMappings.resize(len); for (auto &Mapping: rv->SortedSymbolMappings) { Stream.read((char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin)); Stream.read((char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd)); { auto len = Mapping.Name.size(); Stream.read((char*)&len, sizeof(len)); Mapping.Name.resize(len); Stream.read(Mapping.Name.data(), len); } } } LogMan::Msg::DFmt("GenerateMap: Finished reading index"); return rv; } else { // objdump output parsing, index generation, index file serialization DoGenerate: LogMan::Msg::DFmt("GenerateMap: Generating index for '{}'", GuestSourceFile); std::ifstream Stream(GuestSourceFile); if (!Stream) { LogMan::Msg::DFmt("GenerateMap: Failed to open '{}'", GuestSourceFile); } std::ofstream IndexStream(GuestIndexFile); if (!IndexStream) { LogMan::Msg::DFmt("GenerateMap: Failed to open '{}' for writing", GuestIndexFile); } IndexStream.write("fexsrcindex0", strlen("fexsrcindex0")); // objdump parsing std::string Line; int LineNum = 0; bool PreviousLineWasEmpty = false; uintptr_t LastSymbolOffset{}; uintptr_t CurrentSymbolOffset{}; std::string LastSymbolName; uintptr_t LastOffset{}; uintptr_t CurrentOffset{}; int LastOffsetLine; auto rv = std::make_unique(); rv->SourceFile = GuestSourceFile; auto EndSymbol = [&] { if (LastSymbolOffset) { rv->SortedSymbolMappings.push_back({LastSymbolOffset, CurrentSymbolOffset, LastSymbolName}); // LogMan::Msg::DFmt("Ended Symbol {} - {:x}...{:x}", LastSymbolName, LastSymbolOffset, CurrentSymbolOffset); } LastSymbolOffset = {}; }; auto EndLine = [&] { if (LastOffset) { rv->SortedLineMappings.push_back({LastOffset, CurrentOffset, LastOffsetLine}); // LogMan::Msg::DFmt("Ended Line {} - {:x}...{:x}", LastOffsetLine, LastOffset, CurrentOffset); } LastOffset = {}; }; while (std::getline(Stream, Line)) { LineNum++; auto LineIsEmpty = Line.empty(); if (LineIsEmpty) { PreviousLineWasEmpty = true; } else { // LogMan::Msg::DFmt("Line: '{}'", Line); if (isHEX(Line[0])) { std::string addr; int offs = 1; for (; !isspace(Line[offs]) && offs < Line.size(); offs++) ; if (offs == Line.size()) continue; if (offs != 8 && offs != 16) continue; auto VAOffset = std::strtoul(Line.substr(0, offs).c_str(), nullptr, 16); auto FileOffset = GuestELF.VAToFile(VAOffset); if (FileOffset == 0) { LogMan::Msg::EFmt("File Offset {:x} did not map to file?! {}", VAOffset, Line); } CurrentSymbolOffset = FileOffset; if (PreviousLineWasEmpty) { EndSymbol(); } LastSymbolOffset = CurrentSymbolOffset; for (; Line[offs] != '<' && offs < Line.size(); offs++) ; if (offs == Line.size()) continue; offs++; LastSymbolName = Line.substr(offs, Line.size() - 2 - offs); // LogMan::Msg::DFmt("Symbol {} @ {:x} -> Line {}", LastSymbolName, LastSymbolOffset, LineNum); } else if (isspace(Line[0])) { int offs = 1; for (; isspace(Line[offs]) && offs < Line.size(); offs++) ; if (offs == Line.size()) continue; int start = offs; for (; Line[offs] != ':' && offs < Line.size(); offs++) ; if (offs == Line.size()) continue; if (Line[offs + 1] == '\t') { auto VAOffsetStr = Line.substr(start, offs - start); auto VAOffset = std::strtoul(VAOffsetStr.c_str(), nullptr, 16); auto FileOffset = GuestELF.VAToFile(VAOffset); if (FileOffset == 0) { LogMan::Msg::EFmt("File Offset {:x} did not map to file?! {}", VAOffset, Line); } else { if (LastOffset > FileOffset) { LogMan::Msg::EFmt("File Offset {:x} less than previous {:} ?! {}", FileOffset, LastOffset, Line); } CurrentOffset = FileOffset; EndLine(); LastOffset = CurrentOffset; LastOffsetLine = LineNum; } } } // something else -- keep going } } CurrentOffset = LastOffset + 4; CurrentSymbolOffset = CurrentOffset; EndSymbol(); EndLine(); // Index post processing - entires are sorted for faster lookups std::sort(rv->SortedLineMappings.begin(), rv->SortedLineMappings.end(), [](const auto &lhs, const auto &rhs) { return lhs.FileGuestEnd <= rhs.FileGuestBegin; }); std::sort(rv->SortedSymbolMappings.begin(), rv->SortedSymbolMappings.end(), [](const auto &lhs, const auto &rhs) { return lhs.FileGuestEnd <= rhs.FileGuestBegin; }); // Index serialization { auto len = rv->SourceFile.size(); IndexStream.write((const char*)&len, sizeof(len)); IndexStream.write(rv->SourceFile.c_str(), len); } { auto len = rv->SortedLineMappings.size(); IndexStream.write((const char*)&len, sizeof(len)); for (const auto &Mapping: rv->SortedLineMappings) { IndexStream.write((const char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin)); IndexStream.write((const char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd)); IndexStream.write((const char*)&Mapping.LineNumber, sizeof(Mapping.LineNumber)); } } { auto len = rv->SortedSymbolMappings.size(); IndexStream.write((char*)&len, sizeof(len)); for (const auto &Mapping: rv->SortedSymbolMappings) { IndexStream.write((const char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin)); IndexStream.write((const char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd)); { auto len = Mapping.Name.size(); IndexStream.write((const char*)&len, sizeof(len)); IndexStream.write(Mapping.Name.c_str(), len); } } } LogMan::Msg::DFmt("GenerateMap: Finished generating index", GuestIndexFile); return rv; } } }