Files
FEX-Emu--FEX/Source/Tests/LinuxSyscalls/Syscalls.cpp
T
Ryan Houdek 13446d8b6b Linux: Only install syscall handlers for the arch we launched with
We were registering syscall handlers to the temporary working vectors for
both x86 and x86_64 regardless of which bitness we launched with.

We then only installed the syscall handlers depending on bitness.
This was burning a decent amount of time and some memory on
initialization. Instead just don't register syscall handlers on the
other bitness.

Also stop wasting memory, clear the syscall registration vector after
we've registered everything.

Saves about 28KB of memory per process.
2022-07-19 10:12:09 -07:00

1025 lines
33 KiB
C++

/*
$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 <FEXCore/Config/Config.h>
#include <FEXCore/Core/Context.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Core/CodeLoader.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/HLE/Linux/ThreadManagement.h>
#include <FEXCore/HLE/SyscallHandler.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/CompilerDefs.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Utils/Threads.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <FEXHeaderUtils/ScopedSignalMask.h>
#include <FEXHeaderUtils/TypeDefines.h>
#include <Tests/LinuxSyscalls/SignalDelegator.h>
#include <algorithm>
#include <alloca.h>
#include <functional>
#include <filesystem>
#include <fstream>
#include <memory>
#include <regex>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>
#include <system_error>
#include <syscall.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <unistd.h>
namespace FEXCore::Context {
struct Context;
}
namespace FEX::HLE {
class SignalDelegator;
SyscallHandler *_SyscallHandler{};
void RegisterSyscallInternalNop(int SyscallNumber,
int32_t HostSyscallNumber,
FEXCore::IR::SyscallFlags Flags,
#ifdef DEBUG_STRACE
const std::string& TraceFormatString,
#endif
void* SyscallHandler, int ArgumentCount) {
// Do nothing
}
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<std::string> 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<const char *> 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<char *const *>(ExecveArgs.data()), envp, Args->flags);
}
else {
Result = execve("/proc/self/exe", const_cast<char *const *>(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<uint64_t>(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<uint64_t>(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<void*>(args->args.stack),
args->args.stack_size,
reinterpret_cast<pid_t*>(args->args.parent_tid),
reinterpret_cast<pid_t*>(args->args.child_tid),
reinterpret_cast<void*>(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<std::mutex> 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<uint64_t>(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<void*>(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<void*>(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<void*>(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<FEXCore::HLE::SourcecodeMap> 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<FEXCore::HLE::SourcecodeMap>();
{
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<FEXCore::HLE::SourcecodeMap>();
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;
}
}
}