Files
FEX-Emu--FEX/Source/Tests/FEXLoader.cpp
T
Ryan Houdek dcce9add60 Merge pull request #2334 from Sonicadvance1/fix_execveat
FEXLoader: Adds support for execveat with AT_EMPTY_PATH
2023-01-23 02:32:15 -08:00

533 lines
17 KiB
C++

/*
$info$
tags: Bin|FEXLoader
desc: Glues the ELF loader, FEXCore and LinuxSyscalls to launch an elf under fex
$end_info$
*/
#include "AOT/AOTGenerator.h"
#include "Common/ArgumentLoader.h"
#include "Common/FEXServerClient.h"
#include "ELFCodeLoader2.h"
#include "VDSO_Emulation.h"
#include "Tests/LinuxSyscalls/LinuxAllocator.h"
#include "Tests/LinuxSyscalls/Syscalls.h"
#include "Tests/LinuxSyscalls/x32/Syscalls.h"
#include "Tests/LinuxSyscalls/x64/Syscalls.h"
#include "Tests/LinuxSyscalls/SignalDelegator.h"
#include "Linux/Utils/ELFContainer.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/Core/Context.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/Telemetry.h>
#include <FEXCore/Utils/Threads.h>
#include <FEXCore/Utils/Profiler.h>
#include <atomic>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <elf.h>
#include <fcntl.h>
#include <fstream>
#include <filesystem>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <sys/auxv.h>
#include <sys/resource.h>
#include <sys/select.h>
#include <system_error>
#include <thread>
#include <unistd.h>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include <sys/signal.h>
namespace {
static bool SilentLog;
static int OutputFD {STDERR_FILENO};
static bool ExecutedWithFD {false};
void MsgHandler(LogMan::DebugLevels Level, char const *Message) {
if (SilentLog) {
return;
}
const char *CharLevel{nullptr};
switch (Level) {
case LogMan::NONE:
CharLevel = "NONE";
break;
case LogMan::ASSERT:
CharLevel = "ASSERT";
break;
case LogMan::ERROR:
CharLevel = "ERROR";
break;
case LogMan::DEBUG:
CharLevel = "DEBUG";
break;
case LogMan::INFO:
CharLevel = "Info";
break;
case LogMan::STDOUT:
CharLevel = "STDOUT";
break;
case LogMan::STDERR:
CharLevel = "STDERR";
break;
default:
CharLevel = "???";
break;
}
const auto Output = fmt::format("[{}] {}\n", CharLevel, Message);
write(OutputFD, Output.c_str(), Output.size());
fsync(OutputFD);
}
void AssertHandler(char const *Message) {
if (SilentLog) {
return;
}
const auto Output = fmt::format("[ASSERT] {}\n", Message);
write(OutputFD, Output.c_str(), Output.size());
fsync(OutputFD);
}
} // Anonymous namespace
namespace FEXServerLogging {
int FEXServerFD{};
void MsgHandler(LogMan::DebugLevels Level, char const *Message) {
FEXServerClient::MsgHandler(FEXServerFD, Level, Message);
}
void AssertHandler(char const *Message) {
FEXServerClient::AssertHandler(FEXServerFD, Message);
}
}
void InterpreterHandler(std::string *Filename, std::string const &RootFS, std::vector<std::string> *args) {
// Open the file pointer to the filename and see if we need to find an interpreter
std::fstream File(*Filename, std::fstream::in | std::fstream::binary);
if (!File.is_open()) {
return;
}
File.seekg(0, std::fstream::end);
const auto FileSize = File.tellg();
File.seekg(0, std::fstream::beg);
// Is the file large enough for shebang
if (FileSize <= 2) {
return;
}
// 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.push_back(std::move(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] == '/') {
ShebangProgram = RootFS + ShebangProgram;
}
*Filename = ShebangProgram;
// Insert all the arguments at the start
args->insert(args->begin(), ShebangArguments.begin(), ShebangArguments.end());
// Done here
return;
}
}
void RootFSRedirect(std::string *Filename, std::string const &RootFS) {
auto RootFSLink = ELFCodeLoader2::ResolveRootfsFile(*Filename, RootFS);
std::error_code ec{};
if (std::filesystem::exists(RootFSLink, ec)) {
*Filename = RootFSLink;
}
}
bool RanAsInterpreter(const char *Program) {
return ExecutedWithFD || FEXLOADER_AS_INTERPRETER;
}
bool IsInterpreterInstalled() {
// The interpreter is installed if both the binfmt_misc handlers are available
// Or if we were originally executed with FD. Which means the interpreter is installed
return ExecutedWithFD ||
(access("/proc/sys/fs/binfmt_misc/FEX-x86", F_OK) == 0 &&
access("/proc/sys/fs/binfmt_misc/FEX-x86_64", F_OK) == 0);
}
int main(int argc, char **argv, char **const envp) {
const bool IsInterpreter = RanAsInterpreter(argv[0]);
ExecutedWithFD = getauxval(AT_EXECFD) != 0;
const char* FEXFD = getenv("FEX_EXECVEFD");
const std::string_view FEXFDView = FEXFD ? std::string_view{FEXFD} : std::string_view{};
LogMan::Throw::InstallHandler(AssertHandler);
LogMan::Msg::InstallHandler(MsgHandler);
auto Program = FEX::Config::LoadConfig(
IsInterpreter,
true,
argc, argv, envp,
ExecutedWithFD,
FEXFDView);
if (Program.ProgramPath.empty() && !FEXFD) {
// Early exit if we weren't passed an argument
return 0;
}
auto Args = FEX::ArgLoader::Get();
auto ParsedArgs = FEX::ArgLoader::GetParsedArgs();
// Reload the meta layer
FEXCore::Config::ReloadMetaLayer();
FEXCore::Config::Set(FEXCore::Config::CONFIG_IS_INTERPRETER, IsInterpreter ? "1" : "0");
FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, IsInterpreterInstalled() ? "1" : "0");
// Early check for process stall
// Doesn't use CONFIG_ROOTFS and we don't want it to spin up a squashfs instance
FEX_CONFIG_OPT(StallProcess, STALLPROCESS);
if (StallProcess) {
while (1) {
// Stall this process out forever
select(0, nullptr, nullptr, nullptr, nullptr);
}
}
// Ensure FEXServer is setup before config options try to pull CONFIG_ROOTFS
if (!FEXServerClient::SetupClient(argv[0])) {
LogMan::Msg::EFmt("FEXServerClient: Failure to setup client");
return -1;
}
FEX_CONFIG_OPT(SilentLog, SILENTLOG);
FEX_CONFIG_OPT(AOTIRCapture, AOTIRCAPTURE);
FEX_CONFIG_OPT(AOTIRGenerate, AOTIRGENERATE);
FEX_CONFIG_OPT(AOTIRLoad, AOTIRLOAD);
FEX_CONFIG_OPT(OutputLog, OUTPUTLOG);
FEX_CONFIG_OPT(LDPath, ROOTFS);
FEX_CONFIG_OPT(Environment, ENV);
FEX_CONFIG_OPT(HostEnvironment, HOSTENV);
::SilentLog = SilentLog();
if (::SilentLog) {
LogMan::Throw::UnInstallHandlers();
LogMan::Msg::UnInstallHandlers();
}
else {
auto LogFile = OutputLog();
// If stderr or stdout then we need to dup the FD
// In some cases some applications will close stderr and stdout
// then redirect the FD to either a log OR some cases just not use
// stderr/stdout and the FD will be reused for regular FD ops.
//
// We want to maintain the original output location otherwise we
// can run in to problems of writing to some file
if (LogFile == "stderr") {
OutputFD = dup(STDERR_FILENO);
}
else if (LogFile == "stdout") {
OutputFD = dup(STDOUT_FILENO);
}
else if (LogFile == "server") {
LogMan::Throw::UnInstallHandlers();
LogMan::Msg::UnInstallHandlers();
FEXServerLogging::FEXServerFD = FEXServerClient::RequestLogFD(FEXServerClient::GetServerFD());
if (FEXServerLogging::FEXServerFD != -1) {
LogMan::Throw::InstallHandler(FEXServerLogging::AssertHandler);
LogMan::Msg::InstallHandler(FEXServerLogging::MsgHandler);
}
}
else if (!LogFile.empty()) {
OutputFD = open(LogFile.c_str(), O_CREAT | O_CLOEXEC | O_WRONLY);
}
}
FEXCore::Profiler::Init();
FEXCore::Telemetry::Initialize();
RootFSRedirect(&Program.ProgramPath, LDPath());
InterpreterHandler(&Program.ProgramPath, LDPath(), &Args);
std::error_code ec{};
if (!ExecutedWithFD && !FEXFD && !std::filesystem::exists(Program.ProgramPath, ec)) {
// Early exit if the program passed in doesn't exist
// Will prevent a crash later
fmt::print(stderr, "{}: command not found\n", Program.ProgramPath);
return -ENOEXEC;
}
uint32_t KernelVersion = FEX::HLE::SyscallHandler::CalculateHostKernelVersion();
if (KernelVersion < FEX::HLE::SyscallHandler::KernelVersion(4, 17)) {
// We require 4.17 minimum for MAP_FIXED_NOREPLACE
LogMan::Msg::EFmt("FEXLoader requires kernel 4.17 minimum. Expect problems.");
}
// Before we go any further, set all of our host environment variables that the config has provided
for (auto &HostEnv : HostEnvironment.All()) {
// We are going to keep these alive in memory.
// No need to split the string with setenv
putenv(HostEnv.data());
}
ELFCodeLoader2 Loader{Program.ProgramPath, FEXFDView, LDPath(), Args, ParsedArgs, envp, &Environment};
//FEX::HarnessHelper::ELFCodeLoader Loader{Program.first, LDPath(), Args, ParsedArgs, envp, &Environment};
if (!Loader.ELFWasLoaded()) {
// Loader couldn't load this program for some reason
fmt::print(stderr, "Invalid or Unsupported elf file.\n");
#ifdef _M_ARM_64
fmt::print(stderr, "This is likely due to a misconfigured x86-64 RootFS\n");
fmt::print(stderr, "Current RootFS path set to '{}'\n", LDPath());
std::error_code ec;
if (LDPath().empty() ||
std::filesystem::exists(LDPath(), ec) == false) {
fmt::print(stderr, "RootFS path doesn't exist. This is required on AArch64 hosts\n");
fmt::print(stderr, "Use FEXRootFSFetcher to download a RootFS\n");
}
#endif
return -ENOEXEC;
}
if (ExecutedWithFD) {
// Don't need to canonicalize Program.ProgramPath, Config loader will have resolved this already.
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_APP_FILENAME, Program.ProgramPath);
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_APP_CONFIG_NAME, Program.ProgramName);
}
else if (FEXFD) {
// Anonymous program.
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_APP_FILENAME, "<Anonymous>");
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_APP_CONFIG_NAME, "<Anonymous>");
}
else {
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_APP_FILENAME, std::filesystem::canonical(Program.ProgramPath).string());
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_APP_CONFIG_NAME, Program.ProgramName);
}
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode() ? "1" : "0");
std::unique_ptr<FEX::HLE::MemAllocator> Allocator;
std::vector<FEXCore::Allocator::MemoryRegion> Base48Bit;
if (Loader.Is64BitMode()) {
// Destroy the 48th bit if it exists
Base48Bit = FEXCore::Allocator::Steal48BitVA();
} else {
FEX_CONFIG_OPT(Use32BitAllocator, FORCE32BITALLOCATOR);
if (KernelVersion < FEX::HLE::SyscallHandler::KernelVersion(4, 17)) {
Use32BitAllocator = true;
}
// Setup our userspace allocator
if (!Use32BitAllocator &&
KernelVersion >= FEX::HLE::SyscallHandler::KernelVersion(4, 17)) {
FEXCore::Allocator::SetupHooks();
}
if (Use32BitAllocator) {
Allocator = FEX::HLE::Create32BitAllocator();
}
else {
Allocator = FEX::HLE::CreatePassthroughAllocator();
}
}
// System allocator is now system allocator or FEX
FEXCore::Context::InitializeStaticTables(Loader.Is64BitMode() ? FEXCore::Context::MODE_64BIT : FEXCore::Context::MODE_32BIT);
auto CTX = FEXCore::Context::CreateNewContext();
FEXCore::Context::InitializeContext(CTX);
auto SignalDelegation = std::make_unique<FEX::HLE::SignalDelegator>();
SignalDelegation->RegisterFrontendHostSignalHandler(SIGILL, [&SignalDelegation](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) -> bool {
ucontext_t* _context = (ucontext_t*)ucontext;
auto &mcontext = _context->uc_mcontext;
uint64_t PC{};
#ifdef _M_ARM_64
PC = mcontext.pc;
#else
PC = mcontext.gregs[REG_RIP];
#endif
if (PC == reinterpret_cast<uint64_t>(&FEXCore::Assert::ForcedAssert)) {
// This is a host side assert. Don't deliver this to the guest
// We want to actually break here
SignalDelegation->UninstallHostHandler(Signal);
return true;
}
return false;
}, true);
auto SyscallHandler = Loader.Is64BitMode() ? FEX::HLE::x64::CreateHandler(CTX, SignalDelegation.get())
: FEX::HLE::x32::CreateHandler(CTX, SignalDelegation.get(), std::move(Allocator));
auto Mapper = std::bind_front(&FEX::HLE::SyscallHandler::GuestMmap, SyscallHandler.get());
auto Unmapper = std::bind_front(&FEX::HLE::SyscallHandler::GuestMunmap, SyscallHandler.get());
// Load VDSO in to memory prior to mapping our ELFs.
void* VDSOBase = FEX::VDSO::LoadVDSOThunks(Loader.Is64BitMode(), Mapper);
Loader.SetVDSOBase(VDSOBase);
Loader.CalculateHWCaps(CTX);
if (!Loader.MapMemory(Mapper, Unmapper)) {
// failed to map
LogMan::Msg::EFmt("Failed to map %d-bit elf file.", Loader.Is64BitMode() ? 64 : 32);
return -ENOEXEC;
}
SyscallHandler->SetCodeLoader(&Loader);
auto BRKInfo = Loader.GetBRKInfo();
SyscallHandler->DefaultProgramBreak(BRKInfo.Base, BRKInfo.Size);
FEXCore::Context::SetSignalDelegator(CTX, SignalDelegation.get());
FEXCore::Context::SetSyscallHandler(CTX, SyscallHandler.get());
FEXCore::Context::InitCore(CTX, Loader.DefaultRIP(), Loader.GetStackPointer());
// Pass in our VDSO thunks
FEXCore::Context::AppendThunkDefinitions(CTX, FEX::VDSO::GetVDSOThunkDefinitions());
FEXCore::Context::ExitReason ShutdownReason = FEXCore::Context::ExitReason::EXIT_SHUTDOWN;
// There might already be an exit handler, leave it installed
if(!FEXCore::Context::GetExitHandler(CTX)) {
FEXCore::Context::SetExitHandler(CTX, [&](uint64_t thread, FEXCore::Context::ExitReason reason) {
if (reason != FEXCore::Context::ExitReason::EXIT_DEBUG) {
ShutdownReason = reason;
FEXCore::Context::Stop(CTX);
}
});
}
const bool AOTEnabled = AOTIRLoad() || AOTIRCapture() || AOTIRGenerate();
if (AOTEnabled) {
LogMan::Msg::IFmt("Warning: AOTIR is experimental, and might lead to crashes. "
"Capture doesn't work with programs that fork.");
FEXCore::Context::SetAOTIRLoader(CTX, [](const std::string &fileid) -> int {
auto filepath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "aotir" / (fileid + ".aotir");
return open(filepath.c_str(), O_RDONLY);
});
FEXCore::Context::SetAOTIRWriter(CTX, [](const std::string& fileid) -> std::unique_ptr<std::ofstream> {
auto filepath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "aotir" / (fileid + ".aotir.tmp");
auto AOTWrite = std::make_unique<std::ofstream>(filepath, std::ios::out | std::ios::binary);
if (*AOTWrite) {
std::filesystem::resize_file(filepath, 0);
AOTWrite->seekp(0);
LogMan::Msg::IFmt("AOTIR: Storing {}", fileid);
} else {
LogMan::Msg::IFmt("AOTIR: Failed to store {}", fileid);
}
return AOTWrite;
});
FEXCore::Context::SetAOTIRRenamer(CTX, [](const std::string& fileid) -> void {
auto TmpFilepath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "aotir" / (fileid + ".aotir.tmp");
auto NewFilepath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "aotir" / (fileid + ".aotir");
// Rename the temporary file to atomically update the file
std::filesystem::rename(TmpFilepath, NewFilepath);
});
}
if (AOTIRGenerate()) {
for(auto &Section: Loader.Sections) {
FEX::AOT::AOTGenSection(CTX, Section);
}
} else {
FEXCore::Context::RunUntilExit(CTX);
}
if (AOTEnabled) {
std::filesystem::create_directories(std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "aotir", ec);
if (!ec) {
FEXCore::Context::WriteFilesWithCode(CTX, [](const std::string& fileid, const std::string& filename) {
auto filepath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "aotir" / (fileid + ".path");
int fd = open(filepath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd != -1) {
write(fd, filename.c_str(), filename.size());
close(fd);
}
});
}
if (AOTIRCapture() || AOTIRGenerate()) {
FEXCore::Context::FinalizeAOTIRCache(CTX);
LogMan::Msg::IFmt("AOTIR Cache Stored");
}
}
auto ProgramStatus = FEXCore::Context::GetProgramStatus(CTX);
SyscallHandler.reset();
SignalDelegation.reset();
FEXCore::Context::DestroyContext(CTX);
FEXCore::Context::ShutdownStaticTables();
FEXCore::Threads::Shutdown();
Loader.FreeSections();
FEXCore::Config::Shutdown();
LogMan::Throw::UnInstallHandlers();
LogMan::Msg::UnInstallHandlers();
FEXCore::Allocator::ClearHooks();
FEXCore::Allocator::ReclaimMemoryRegion(Base48Bit);
// Allocator is now original system allocator
FEXCore::Telemetry::Shutdown(Program.ProgramName);
FEXCore::Profiler::Shutdown();
if (ShutdownReason == FEXCore::Context::ExitReason::EXIT_SHUTDOWN) {
return ProgramStatus;
}
else {
return -64 | ShutdownReason;
}
}