mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-11 14:00:38 +02:00
Mesa uses this to determine what the executable name is in a process for application profiles. Without thunking, the guest glibc sets this correctly, with thunking mesa would pick up `FEX` instead of the app. With this fixed, it fixes Dead Island rendering when thunks are enabled. Plenty of other games in Mesa's application profiles that it would fix as well.
1059 lines
40 KiB
C++
1059 lines
40 KiB
C++
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include "ArchHelpers/UContext.h"
|
|
#include "CodeLoader.h"
|
|
#include "Common/FDUtils.h"
|
|
#include "FEXCore/Utils/Allocator.h"
|
|
#include "LinuxSyscalls/Syscalls.h"
|
|
#include "VDSO_Emulation.h"
|
|
#include "Linux/Utils/ELFParser.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include <FEXCore/Core/CoreState.h>
|
|
#include <FEXCore/Utils/MathUtils.h>
|
|
#include <FEXCore/Core/X86Enums.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
#include <FEXCore/Utils/TypeDefines.h>
|
|
#include <FEXCore/Utils/FileLoading.h>
|
|
#include <FEXCore/fextl/list.h>
|
|
#include <FEXCore/fextl/string.h>
|
|
#include <FEXCore/fextl/vector.h>
|
|
#include <FEXHeaderUtils/Syscalls.h>
|
|
#include <FEXHeaderUtils/SymlinkChecks.h>
|
|
|
|
#include <elf.h>
|
|
#include <fcntl.h>
|
|
#include <fmt/format.h>
|
|
#include <sys/auxv.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/personality.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/random.h>
|
|
#include <linux/prctl.h>
|
|
|
|
#define PAGE_START(x) ((x) & ~(uintptr_t)(4095))
|
|
#define PAGE_OFFSET(x) ((x) & 4095)
|
|
#define PAGE_ALIGN(x) (((x) + 4095) & ~(uintptr_t)(4095))
|
|
|
|
class ELFCodeLoader final : public FEX::CodeLoader {
|
|
ELFParser MainElf {};
|
|
ELFParser InterpElf {};
|
|
|
|
bool ElfValid {false};
|
|
bool ExecutableStack {false};
|
|
bool ExecuteAll {false};
|
|
bool HasStackHeader {false};
|
|
uintptr_t MainElfBase {};
|
|
uintptr_t InterpeterElfBase {};
|
|
uintptr_t MainElfEntrypoint {};
|
|
uintptr_t Entrypoint {};
|
|
uintptr_t BrkStart {};
|
|
uintptr_t StackPointer {};
|
|
|
|
// This calculates the map size for ET_DYN type ELF files.
|
|
// Can't be used for ET_EXEC ELF files because they can have large virtual mapping holes.
|
|
static size_t CalculateDYNELFSize(const fextl::vector<Elf64_Phdr>& headers) {
|
|
bool had_pt_load = false;
|
|
size_t min_map_address = ~0ULL;
|
|
size_t max_map_address = 0;
|
|
for (const auto& it : headers) {
|
|
if (it.p_type != PT_LOAD) {
|
|
// Skip everything but PT_LOAD.
|
|
continue;
|
|
}
|
|
|
|
had_pt_load = true;
|
|
min_map_address = std::min(min_map_address, PAGE_START(it.p_vaddr));
|
|
max_map_address = std::max(max_map_address, it.p_vaddr + it.p_memsz);
|
|
}
|
|
|
|
if (!had_pt_load) {
|
|
// No load segments, so need to be safe and return zero.
|
|
return 0;
|
|
}
|
|
|
|
return FEXCore::AlignUp(max_map_address - min_map_address, FEXCore::Utils::FEX_PAGE_SIZE);
|
|
}
|
|
|
|
bool MapFile(const ELFParser& file, uintptr_t Base, const Elf64_Phdr& Header, int prot, int flags,
|
|
FEX::HLE::SyscallMmapInterface* const Handler, FEXCore::Core::InternalThreadState* Thread) {
|
|
|
|
auto addr = Base + PAGE_START(Header.p_vaddr);
|
|
auto size = Header.p_filesz + PAGE_OFFSET(Header.p_vaddr);
|
|
auto off = Header.p_offset - PAGE_OFFSET(Header.p_vaddr);
|
|
|
|
size = PAGE_ALIGN(size);
|
|
if (size == 0) {
|
|
// PT_LOAD section without a file size
|
|
// Will need to have a memory size that is not zero instead
|
|
return true;
|
|
}
|
|
|
|
void* rv = Handler->GuestMmap(Thread, (void*)addr, size, prot, flags, file.fd, off);
|
|
|
|
if (FEX::HLE::HasSyscallError(rv)) {
|
|
// uhoh, something went wrong
|
|
LogMan::Msg::EFmt("MapFile: Some elf mapping failed, {}, fd: {}\n", errno, file.fd);
|
|
return false;
|
|
} else {
|
|
char Tmp[PATH_MAX];
|
|
auto PathLength = FEX::get_fdpath(file.fd, Tmp);
|
|
if (PathLength != -1) {
|
|
Sections.push_back({Base, (uintptr_t)rv, size, (off_t)off, fextl::string(Tmp, PathLength), (prot & PROT_EXEC) != 0});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
int MapFlags(const Elf64_Phdr& Header) {
|
|
int rv = 0;
|
|
|
|
if (Header.p_flags & PF_R) {
|
|
rv |= PROT_READ;
|
|
}
|
|
|
|
if (Header.p_flags & PF_W) {
|
|
rv |= PROT_WRITE;
|
|
}
|
|
|
|
if (Header.p_flags & PF_X) {
|
|
rv |= PROT_EXEC;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
std::optional<uintptr_t> LoadElfFile(ELFParser& Elf, uintptr_t* BrkBase, FEX::HLE::SyscallMmapInterface* const Handler,
|
|
FEXCore::Core::InternalThreadState* Thread, uint64_t LoadHint = 0) {
|
|
|
|
uintptr_t LoadBase = 0;
|
|
uintptr_t BrkLoadBase = 0;
|
|
const bool DynELF = Elf.ehdr.e_type == ET_DYN;
|
|
const bool NeedsLateBRKMap = BrkBase && !DynELF;
|
|
|
|
if (DynELF) {
|
|
// Allocate a base address plus BRK padding.
|
|
auto TotalSize = CalculateDYNELFSize(Elf.phdrs) + (BrkBase ? BRK_SIZE : 0);
|
|
LoadBase =
|
|
(uintptr_t)Handler->GuestMmap(Thread, reinterpret_cast<void*>(LoadHint), TotalSize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
|
if (FEX::HLE::HasSyscallError(LoadBase)) {
|
|
return {};
|
|
}
|
|
|
|
// fprintf(stderr, "elf %d: %lx-%lx\n", Elf.fd, LoadBase, LoadBase + TotalSize);
|
|
if (BrkBase) {
|
|
BrkLoadBase = LoadBase + (TotalSize - BRK_SIZE);
|
|
}
|
|
}
|
|
|
|
for (const auto& Header : Elf.phdrs) {
|
|
if (Header.p_type != PT_LOAD) {
|
|
continue;
|
|
}
|
|
|
|
int MapProt = MapFlags(Header);
|
|
int MapType = MAP_PRIVATE | MAP_DENYWRITE | MAP_FIXED;
|
|
|
|
if (!MapFile(Elf, LoadBase, Header, MapProt, MapType, Handler, Thread)) {
|
|
return {};
|
|
}
|
|
|
|
if (Header.p_memsz > Header.p_filesz) {
|
|
// clear bss
|
|
auto BSSStart = LoadBase + Header.p_vaddr + Header.p_filesz;
|
|
auto BSSPageStart = PAGE_ALIGN(BSSStart);
|
|
auto BSSPageEnd = PAGE_ALIGN(LoadBase + Header.p_vaddr + Header.p_memsz);
|
|
|
|
// Only clear padding bytes if the section is writable
|
|
if (Header.p_flags & PF_W) {
|
|
memset((void*)BSSStart, 0, BSSPageStart - BSSStart);
|
|
}
|
|
|
|
if (BSSPageStart != BSSPageEnd) {
|
|
auto bss = Handler->GuestMmap(Thread, (void*)BSSPageStart, BSSPageEnd - BSSPageStart, MapProt, MapType | MAP_ANONYMOUS, -1, 0);
|
|
if (FEX::HLE::HasSyscallError(bss)) {
|
|
LogMan::Msg::EFmt("Failed to allocate BSS @ {}, {}\n", fmt::ptr(bss), errno);
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NeedsLateBRKMap) {
|
|
// Keep track of highest address for BRK in the case of non-dynamic ELF files.
|
|
auto memend = LoadBase + Header.p_vaddr + Header.p_memsz;
|
|
|
|
// track elf_brk
|
|
if (memend > BrkLoadBase) {
|
|
BrkLoadBase = FEXCore::AlignUp(memend, FEXCore::Utils::FEX_PAGE_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NeedsLateBRKMap) {
|
|
// Map the BRK after ELF if possible.
|
|
BrkLoadBase =
|
|
(uintptr_t)Handler->GuestMmap(Thread, reinterpret_cast<void*>(BrkLoadBase), BRK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
|
if (FEX::HLE::HasSyscallError(BrkLoadBase)) {
|
|
// This isn't a catastrophic failure. This just means the BRK conflicted with the ELF.
|
|
BrkLoadBase = 0;
|
|
}
|
|
}
|
|
|
|
if (BrkBase) {
|
|
*BrkBase = BrkLoadBase;
|
|
}
|
|
|
|
return LoadBase;
|
|
}
|
|
|
|
static bool GetRandom(void* Data, size_t DataSize) {
|
|
ssize_t Result {};
|
|
do {
|
|
// This is guaranteed to not be interrupted by a signal,
|
|
// since fewer than 256 bytes of RNG data are requested
|
|
Result = getrandom(Data, DataSize, 0);
|
|
} while (Result != -1 && Result != DataSize);
|
|
|
|
return Result != -1;
|
|
}
|
|
|
|
public:
|
|
|
|
static fextl::string ResolveRootfsFile(const fextl::string& File, fextl::string RootFS) {
|
|
// If the path is relative then just run that
|
|
if (File[0] != '/') {
|
|
return File;
|
|
}
|
|
|
|
fextl::string RootFSLink = RootFS + File;
|
|
|
|
char Filename[PATH_MAX];
|
|
while (FHU::Symlinks::IsSymlink(RootFSLink.c_str())) {
|
|
// Do some special handling if the RootFS's linker is a symlink
|
|
// Ubuntu's rootFS by default provides an absolute location symlink to the linker
|
|
// Resolve this around back to the rootfs
|
|
auto SymlinkPath = FHU::Symlinks::ResolveSymlink(RootFSLink.c_str(), Filename);
|
|
if (SymlinkPath.starts_with('/')) {
|
|
RootFSLink = RootFS;
|
|
RootFSLink += SymlinkPath;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return RootFSLink;
|
|
}
|
|
|
|
struct LoadedSection {
|
|
uintptr_t ElfBase;
|
|
uintptr_t Base;
|
|
size_t Size;
|
|
off_t Offs;
|
|
fextl::string Filename;
|
|
bool Executable;
|
|
};
|
|
|
|
fextl::vector<LoadedSection> Sections;
|
|
|
|
ELFCodeLoader(const fextl::string& Filename, int ProgramFDFromEnv, const fextl::string& RootFS, const fextl::vector<fextl::string>& args,
|
|
const fextl::vector<fextl::string>& ParsedArgs, char** const envp = nullptr,
|
|
FEXCore::Config::Value<FEXCore::Config::StringArrayType>* AdditionalEnvp = nullptr, bool SkipInterpreter = false) {
|
|
ApplicationArgs = args;
|
|
|
|
bool LoadedWithFD = false;
|
|
int FD = getauxval(AT_EXECFD);
|
|
|
|
if (ProgramFDFromEnv != -1) {
|
|
// If we passed the execve FD to us then use that.
|
|
FD = ProgramFDFromEnv;
|
|
}
|
|
|
|
// If we are provided an EXECFD then attempt to execute that first
|
|
// This happens in the case of binfmt_misc usage
|
|
if (FD != 0) {
|
|
if (!MainElf.ReadElf(FD)) {
|
|
return;
|
|
}
|
|
LoadedWithFD = true;
|
|
} else {
|
|
if (!MainElf.ReadElf(ResolveRootfsFile(Filename, RootFS)) && !MainElf.ReadElf(Filename)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we have loaded with EXECFD then we have binfmt_misc preserve argv[0] also set
|
|
// This adds an additional argument to our argument list that we need to ignore
|
|
// argv[0] = FEX
|
|
// argv[1] = <Path to binary>
|
|
// argv[2] = <original user typed path to binary>
|
|
// If our kernel if v5.12 or higher then
|
|
// We can check if this exists by checking auxv[AT_FLAGS] for AT_FLAGS_PRESERVE_ARGV0
|
|
// Else we need to make an assumption that if we were loaded with FD that we have preserve enabled
|
|
|
|
uint64_t AtFlags = getauxval(AT_FLAGS);
|
|
#ifndef AT_FLAGS_PRESERVE_ARGV0
|
|
#define AT_FLAGS_PRESERVE_ARGV0 1
|
|
#endif
|
|
uint32_t HostKernel = FEX::HLE::SyscallHandler::CalculateHostKernelVersion();
|
|
if ((HostKernel >= FEX::HLE::SyscallHandler::KernelVersion(5, 12, 0) && (AtFlags & AT_FLAGS_PRESERVE_ARGV0)) || LoadedWithFD) {
|
|
|
|
// Erase the initial argument from the list in this case
|
|
ApplicationArgs.erase(ApplicationArgs.begin());
|
|
}
|
|
|
|
// Append any additional arguments from config
|
|
const auto AdditionalArgs = AdditionalArguments.All();
|
|
ApplicationArgs.insert(ApplicationArgs.end(), AdditionalArgs.begin(), AdditionalArgs.end());
|
|
|
|
if (!MainElf.InterpreterElf.empty() && !SkipInterpreter) {
|
|
if (!InterpElf.ReadElf(ResolveRootfsFile(MainElf.InterpreterElf, RootFS)) && !InterpElf.ReadElf(MainElf.InterpreterElf)) {
|
|
return;
|
|
}
|
|
|
|
if (!InterpElf.InterpreterElf.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (InterpElf.type != MainElf.type) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ElfValid = true;
|
|
|
|
if (envp) {
|
|
// If we had envp passed in then make sure to set it up on the guest
|
|
for (size_t i = 0;; ++i) {
|
|
if (envp[i] == nullptr) {
|
|
break;
|
|
}
|
|
EnvironmentVariables.emplace_back(envp[i]);
|
|
}
|
|
}
|
|
|
|
if (AdditionalEnvp) {
|
|
const auto EnvpList = AdditionalEnvp->All();
|
|
EnvironmentVariables.insert(EnvironmentVariables.end(), EnvpList.begin(), EnvpList.end());
|
|
}
|
|
|
|
if (InjectLibSegFault()) {
|
|
EnvironmentVariables.emplace_back("LD_PRELOAD=libSegFault.so");
|
|
}
|
|
|
|
// Calculate argument and envp backing sizes
|
|
for (const auto& Arg : ApplicationArgs) {
|
|
ArgumentBackingSize += Arg.size() + 1;
|
|
}
|
|
for (const auto& EnvVar : EnvironmentVariables) {
|
|
EnvironmentBackingSize += EnvVar.size() + 1;
|
|
}
|
|
|
|
for (const auto& Arg : ParsedArgs) {
|
|
LoaderArgs.emplace_back(Arg.c_str());
|
|
}
|
|
}
|
|
|
|
void FreeSections() {
|
|
Sections.clear();
|
|
}
|
|
|
|
uint64_t StackSize() const override {
|
|
return STACK_SIZE;
|
|
}
|
|
uint64_t GetStackPointer() const override {
|
|
return StackPointer;
|
|
}
|
|
uint64_t DefaultRIP() const override {
|
|
return Entrypoint;
|
|
}
|
|
|
|
struct auxv32_t {
|
|
uint32_t key;
|
|
uint32_t val;
|
|
};
|
|
|
|
struct auxv_t {
|
|
uint64_t key;
|
|
uint64_t val;
|
|
};
|
|
|
|
int GetMainElfFD() const {
|
|
return MainElf.fd;
|
|
}
|
|
|
|
std::optional<uintptr_t> LoadMainElfFile(uintptr_t* BrkBase, FEX::HLE::SyscallMmapInterface* const Handler,
|
|
FEXCore::Core::InternalThreadState* Thread, uint64_t LoadHint = 0) {
|
|
return LoadElfFile(MainElf, BrkBase, Handler, Thread, LoadHint);
|
|
}
|
|
|
|
bool MapMemory(FEX::HLE::SyscallMmapInterface* const Handler, FEXCore::Core::InternalThreadState* Thread) {
|
|
for (const auto& Header : MainElf.phdrs) {
|
|
if (Header.p_type == PT_GNU_STACK) {
|
|
HasStackHeader = true;
|
|
if (Header.p_flags & PF_X) {
|
|
ExecutableStack = true;
|
|
}
|
|
}
|
|
|
|
// We ignore LOPROC..HIPROC here, kernel has a platform specific hook about it
|
|
// Both for the main and the interpreter elf
|
|
}
|
|
|
|
if (!HasStackHeader && !Is64BitMode()) {
|
|
// 32-bit behavior
|
|
ExecutableStack = true;
|
|
ExecuteAll = true;
|
|
}
|
|
|
|
// Set the process personality here
|
|
// Also, what about ADDR_LIMIT_3GB & co ?
|
|
uint32_t Personality = personality(~0ULL);
|
|
Personality |= ExecuteAll ? READ_IMPLIES_EXEC : 0;
|
|
if (-1 == personality(Personality)) {
|
|
LogMan::Msg::EFmt("Setting personality failed");
|
|
return false;
|
|
}
|
|
|
|
if (Thread) {
|
|
// Update the thread persona.
|
|
auto ThreadObject = static_cast<FEX::HLE::ThreadStateObject*>(Thread->FrontendPtr);
|
|
ThreadObject->persona = Personality;
|
|
}
|
|
|
|
// What about ASLR and such ?
|
|
// ADDR_LIMIT_3GB STACK -> 0xc0000000 else -> 0xFFFFe000
|
|
|
|
// map stack here, so that nothing gets mapped there
|
|
// This works with both 64-bit and 32-bit. The mapper will only give us a function in the correct region
|
|
//
|
|
// MAP_GROWSDOWN is required here. The default stack pointer allocated by the kernel is mapped with it.
|
|
// Some libraries (like libfmod) will have a PT_GNU_STACK with executable stack bit set
|
|
// On dlopen glibc will check its current stack allocation permission bits (using internal expectations of allocation, not
|
|
// /proc/self/maps) If stack hasn't been allocated as executable then it will proceed to mprotect the range with the executable bit set
|
|
// Then it will mprotect the base stack page with `PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN`
|
|
// If the original stack memory region wasn't allocated with MAP_GROWSDOWN then the mprotect with PROT_GROWSDOWN will fail with EINVAL
|
|
//
|
|
// This is still technically a memory leak if the stack grows, but since the primary thread's stack only gets destroyed on process
|
|
// close, this is fine.
|
|
|
|
// Stacks need to be allocated at the hint location just like on a real x86 system.
|
|
// These are 128MB regions on both x86-64 and x86.
|
|
//
|
|
// These are required to be in the correct location taking up the appropriate 128MB of space, otherwise the wine preloader crashes FEX.
|
|
// This is due to the wine-preloader hardcoding addresses [0x7FFFFE000000 - 0x7FFFFFFF0000) as a top-down
|
|
// allocation region. They use mmap with MAP_FIXED, ignoring any previously mapped area at that location and overwriting it.
|
|
// Wine-preloader is expecting to allocate 32MB out of the total 128MB stack space in this case. Leaving 96MB for the application.
|
|
//
|
|
// If FEX doesn't allocate the stack in this region (nullptr mmap hint) then later allocations that FEX does will /eventually/
|
|
// end up inside of this address space that wine allocates. This usually ends up being a JIT CodeBuffer, which zeroes the memory and
|
|
// faults with a SIGILL.
|
|
//
|
|
// On the upside, this more accurately emulates how the kernel allocates stack space for the application when hinting at the location.
|
|
//
|
|
void* StackPointerBase {};
|
|
auto VASize = FEXCore::Allocator::DetermineVASize();
|
|
uint64_t StackHint {};
|
|
if (Is64BitMode()) {
|
|
if (VASize > 47) {
|
|
// If VA size is at least as large as minimum x86 specification, then set to max.
|
|
VASize = 47;
|
|
}
|
|
|
|
// Calculate the highest point the stack could go.
|
|
StackHint = (1ULL << VASize) - FULL_STACK_SIZE;
|
|
} else {
|
|
// Needs to be under the 4GB VA space.
|
|
StackHint = 0x1'0000'0000ULL - FULL_STACK_SIZE;
|
|
}
|
|
|
|
auto PageSize = sysconf(_SC_PAGESIZE);
|
|
PageSize = PageSize > 0 ? PageSize : FEXCore::Utils::FEX_PAGE_SIZE;
|
|
|
|
do {
|
|
// Allocate the base of the full 128MB stack range.
|
|
StackPointerBase = Handler->GuestMmap(Thread, reinterpret_cast<void*>(StackHint), FULL_STACK_SIZE, PROT_NONE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN | MAP_NORESERVE | MAP_FIXED_NOREPLACE, -1, 0);
|
|
// Scan-downward until we fit.
|
|
StackHint -= PageSize;
|
|
} while (FEX::HLE::HasSyscallError(StackPointerBase) && static_cast<int64_t>(StackHint) > 0);
|
|
|
|
if (FEX::HLE::HasSyscallError(StackPointerBase)) {
|
|
LogMan::Msg::EFmt("Allocating stack failed");
|
|
return false;
|
|
}
|
|
|
|
// Allocate with permissions the 8MB of regular stack size.
|
|
StackPointer = reinterpret_cast<uintptr_t>(Handler->GuestMmap(
|
|
Thread, reinterpret_cast<void*>(reinterpret_cast<uint64_t>(StackPointerBase) + FULL_STACK_SIZE - StackSize()), StackSize(),
|
|
PROT_READ | PROT_WRITE | (ExecutableStack ? PROT_EXEC : 0), MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0));
|
|
|
|
if (FEX::HLE::HasSyscallError(StackPointer)) {
|
|
LogMan::Msg::EFmt("Allocating stack failed");
|
|
return false;
|
|
}
|
|
|
|
// Load the interpreter ELF first.
|
|
// This allows the top-down allocation of the kernel to put this at the top of the VA space.
|
|
// This matches behaviour of native execution more closely.
|
|
//
|
|
// eg:
|
|
// 555555554000-555555558000 r--p 00000000 103:0a 1311400 /usr/bin/ls
|
|
// 555555558000-55555556c000 r-xp 00004000 103:0a 1311400 /usr/bin/ls
|
|
// 55555556c000-555555574000 r--p 00018000 103:0a 1311400 /usr/bin/ls
|
|
// 555555575000-555555577000 rw-p 00020000 103:0a 1311400 /usr/bin/ls
|
|
// 555555577000-555555578000 rw-p 00000000 00:00 0 [heap]
|
|
// 7ffff7fbb000-7ffff7fbd000 rw-p 00000000 00:00 0
|
|
// 7ffff7fbd000-7ffff7fc1000 r--p 00000000 00:00 0 [vvar]
|
|
// 7ffff7fc1000-7ffff7fc3000 r-xp 00000000 00:00 0 [vdso]
|
|
// 7ffff7fc3000-7ffff7fc5000 r--p 00000000 103:0a 1316948 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
// 7ffff7fc5000-7ffff7fef000 r-xp 00002000 103:0a 1316948 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
// 7ffff7fef000-7ffff7ffa000 r--p 0002c000 103:0a 1316948 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
// 7ffff7ffb000-7ffff7fff000 rw-p 00037000 103:0a 1316948 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
// 7ffffffdd000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
|
|
// ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
|
|
//
|
|
// ARM:
|
|
// 55ccaf8b1000-55ccaf8b5000 r--p 00000000 00:2a 4 /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
|
|
// 55ccaf8b5000-55ccaf8c9000 r-xp 00004000 00:2a 4 /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
|
|
// 55ccaf8c9000-55ccaf8d1000 r--p 00018000 00:2a 4 /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
|
|
// 55ccaf8d1000-55ccaf8d2000 ---p 00000000 00:00 0
|
|
// 55ccaf8d2000-55ccaf8d4000 rw-p 00020000 00:2a 4 /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
|
|
// 55ccaf8d4000-55ccb00d5000 rw-p 00000000 00:00 0
|
|
// <... Snip of misc allocations ...>
|
|
// 7fffff6c2000-7fffff6c4000 r--p 00000000 00:2a 22 /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff6c4000-7fffff6ee000
|
|
// r-xp 00002000 00:2a 22 /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff6ee000-7fffff6f9000
|
|
// r--p 0002c000 00:2a 22 /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff6f9000-7fffff6fa000
|
|
// ---p 00000000 00:00 0 7fffff6fa000-7fffff6fe000 rw-p 00037000 00:2a 22
|
|
// /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff7fe000-7fffffffe000 rw-p 00000000 00:00 0 7fffffffe000-7ffffffff000
|
|
// r--p 00000000 08:82 7082611 /usr/share/fex-emu/GuestThunks/libVDSO-guest.so 7ffffffff000-800000000000 rw-p
|
|
// 00000000 00:00 0
|
|
uint64_t ELFLoadHint = 0;
|
|
|
|
if (!MainElf.InterpreterElf.empty()) {
|
|
uint64_t InterpLoadBase = 0;
|
|
if (auto elf = LoadElfFile(InterpElf, nullptr, Handler, Thread)) {
|
|
InterpLoadBase = *elf;
|
|
} else {
|
|
LogMan::Msg::EFmt("Failed to load interpreter elf file");
|
|
return false;
|
|
}
|
|
|
|
InterpeterElfBase = InterpLoadBase + InterpElf.phdrs.front().p_vaddr - InterpElf.phdrs.front().p_offset;
|
|
Entrypoint = InterpLoadBase + InterpElf.ehdr.e_entry;
|
|
|
|
// If the ELF has an interpreter and is dynamic then we should provide a address hint for loading.
|
|
// The kernel calculates this `load_bias` by dividing the task size by three then multiplying by two.
|
|
// It then also offsets by a random number for ASLR purposes.
|
|
//
|
|
// Random number that gets added to the base needs to be in the number of bits (multiplied by pages):
|
|
// 64-bit: [28, 32] bits
|
|
// 32-bit: [8, 16] bits
|
|
// By default the /minimum/ number of bits is used here.
|
|
constexpr uint64_t TASK_SIZE_64 = (1ULL << 47);
|
|
constexpr uint64_t TASK_SIZE_32 = (1ULL << 32);
|
|
if (Is64BitMode()) {
|
|
// Ensure that if we are running on a 36-bit VA system, we don't try hinting that an ELF should
|
|
// live way outside the VA space.
|
|
uint64_t HostVASize = 1ULL << FEXCore::Allocator::DetermineVASize();
|
|
ELFLoadHint = std::min(HostVASize, TASK_SIZE_64) / 3 * 2;
|
|
} else {
|
|
ELFLoadHint = TASK_SIZE_32 / 3 * 2;
|
|
}
|
|
#define ASLR_LOAD
|
|
#ifdef ASLR_LOAD
|
|
// Only enable ASLR randomization if the personality has it enabled.
|
|
bool NoRandomize = (Personality & ADDR_NO_RANDOMIZE) == ADDR_NO_RANDOMIZE;
|
|
|
|
if (!NoRandomize) {
|
|
constexpr uint64_t ASLR_BITS_64 = 28;
|
|
constexpr uint64_t ASLR_BITS_32 = 8;
|
|
uint64_t ASLR_Offset {};
|
|
if (!GetRandom(&ASLR_Offset, sizeof(ASLR_Offset))) {
|
|
// getrandom failed for some reason.
|
|
ASLR_Offset = 0;
|
|
LogMan::Msg::EFmt("RNG failed. ASLR will not work.");
|
|
}
|
|
|
|
if (Is64BitMode()) {
|
|
ASLR_Offset &= (1ULL << ASLR_BITS_64) - 1;
|
|
} else {
|
|
ASLR_Offset &= (1ULL << ASLR_BITS_32) - 1;
|
|
}
|
|
|
|
ASLR_Offset <<= FEXCore::Utils::FEX_PAGE_SHIFT;
|
|
ELFLoadHint += ASLR_Offset;
|
|
}
|
|
#endif
|
|
// Align the mapping
|
|
ELFLoadHint &= FEXCore::Utils::FEX_PAGE_MASK;
|
|
}
|
|
|
|
// load the main elf
|
|
|
|
uintptr_t LoadBase = 0;
|
|
|
|
if (auto elf = LoadElfFile(MainElf, &BrkStart, Handler, Thread, ELFLoadHint)) {
|
|
LoadBase = *elf;
|
|
if (MainElf.ehdr.e_type == ET_DYN) {
|
|
BaseOffset = LoadBase;
|
|
}
|
|
} else {
|
|
LogMan::Msg::EFmt("Failed to load elf file");
|
|
return false;
|
|
}
|
|
|
|
if (BrkStart) {
|
|
// BRK usually comes directly after where the ELF is loaded.
|
|
// If a value was returned then we have mapped the entire `BRK_SIZE` and need to change protections.
|
|
BrkStart =
|
|
(uint64_t)Handler->GuestMmap(Thread, (void*)BrkStart, BRK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
|
|
if (FEX::HLE::HasSyscallError(BrkStart)) {
|
|
LogMan::Msg::EFmt("Failed to allocate BRK @ {:x}, {}\n", BrkStart, errno);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MainElfBase = LoadBase + MainElf.phdrs.front().p_vaddr - MainElf.phdrs.front().p_offset;
|
|
MainElfEntrypoint = LoadBase + MainElf.ehdr.e_entry;
|
|
|
|
if (MainElf.InterpreterElf.empty()) {
|
|
InterpeterElfBase = 0;
|
|
Entrypoint = MainElfEntrypoint;
|
|
}
|
|
|
|
// All done
|
|
|
|
// Setup AuxVars
|
|
AuxVariables.emplace_back(auxv_t {11, getauxval(AT_UID)}); // AT_UID
|
|
AuxVariables.emplace_back(auxv_t {12, getauxval(AT_EUID)}); // AT_EUID
|
|
AuxVariables.emplace_back(auxv_t {13, getauxval(AT_GID)}); // AT_GID
|
|
AuxVariables.emplace_back(auxv_t {14, getauxval(AT_EGID)}); // AT_EGID
|
|
AuxVariables.emplace_back(auxv_t {17, getauxval(AT_CLKTCK)}); // AT_CLKTIK
|
|
AuxVariables.emplace_back(auxv_t {6, FEXCore::Utils::FEX_PAGE_SIZE}); // AT_PAGESIZE
|
|
AuxRandom = &AuxVariables.emplace_back(auxv_t {25, ~0ULL}); // AT_RANDOM
|
|
AuxVariables.emplace_back(auxv_t {23, getauxval(AT_SECURE)}); // AT_SECURE
|
|
AuxVariables.emplace_back(auxv_t {8, 0}); // AT_FLAGS
|
|
AuxVariables.emplace_back(auxv_t {5, MainElf.phdrs.size()}); // AT_PHNUM
|
|
AuxVariables.emplace_back(auxv_t {16, HWCap}); // AT_HWCAP
|
|
AuxVariables.emplace_back(auxv_t {26, HWCap2}); // AT_HWCAP2
|
|
AuxVariables.emplace_back(auxv_t {51, CalculateSignalStackSize()}); // AT_MINSIGSTKSZ
|
|
AuxPlatform = &AuxVariables.emplace_back(auxv_t {24, ~0ULL}); // AT_PLATFORM
|
|
AuxExecFN = &AuxVariables.emplace_back(auxv_t {AT_EXECFN, ~0ULL}); // AT_EXECFN
|
|
|
|
if (Is64BitMode()) {
|
|
AuxVariables.emplace_back(auxv_t {4, 0x38}); // AT_PHENT
|
|
} else {
|
|
AuxVariables.emplace_back(auxv_t {4, 0x20}); // AT_PHENT
|
|
|
|
auto VSyscallEntry = FEX::VDSO::GetVSyscallEntry(VDSOBase);
|
|
if (!VSyscallEntry) [[unlikely]] {
|
|
// If the VDSO thunk doesn't exist then we might not have a vsyscall entry.
|
|
// Newer glibc requires vsyscall to exist now. So let's allocate a buffer and stick a vsyscall in to it.
|
|
auto VSyscallPage =
|
|
Handler->GuestMmap(Thread, nullptr, FEXCore::Utils::FEX_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
|
constexpr static uint8_t VSyscallCode[] = {
|
|
0xcd, 0x80, // int 0x80
|
|
0xc3, // ret
|
|
};
|
|
memcpy(VSyscallPage, VSyscallCode, sizeof(VSyscallCode));
|
|
mprotect(VSyscallPage, FEXCore::Utils::FEX_PAGE_SIZE, PROT_READ);
|
|
VSyscallEntry = reinterpret_cast<uint64_t>(VSyscallPage);
|
|
}
|
|
|
|
AuxVariables.emplace_back(auxv_t {32, VSyscallEntry}); // AT_SYSINFO - Entry point to syscall
|
|
}
|
|
|
|
if (VDSOBase) {
|
|
AuxVariables.emplace_back(auxv_t {33, reinterpret_cast<uint64_t>(VDSOBase)}); // AT_SYSINFO_EHDR - Address of the start of VDSO
|
|
}
|
|
|
|
AuxVariables.emplace_back(auxv_t {3, MainElfBase + MainElf.ehdr.e_phoff}); // Program header
|
|
AuxVariables.emplace_back(auxv_t {7, InterpeterElfBase}); // AT_BASE - Interpreter address
|
|
AuxVariables.emplace_back(auxv_t {9, MainElfEntrypoint}); // AT_ENTRY
|
|
|
|
AuxVariables.emplace_back(auxv_t {0, 0}); // Null ender
|
|
|
|
SetupStack();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CloseFDs() {
|
|
MainElf.Closefd();
|
|
InterpElf.Closefd();
|
|
}
|
|
|
|
// Helper for stack setup
|
|
template<typename PointerType, typename AuxType, size_t PointerSize>
|
|
static void SetupPointers(uintptr_t StackPointer, uint64_t AuxVOffset, uint64_t ArgumentOffset, uint64_t EnvpOffset,
|
|
const fextl::vector<fextl::string>& Args, const fextl::vector<fextl::string>& EnvironmentVariables,
|
|
const fextl::list<auxv_t>& AuxVariables, uint64_t* AuxTabBase, uint64_t* AuxTabSize) {
|
|
// Pointer list offsets
|
|
PointerType* ArgumentPointers = reinterpret_cast<PointerType*>(StackPointer + PointerSize);
|
|
PointerType* PadPointers = reinterpret_cast<PointerType*>(StackPointer + PointerSize + Args.size() * PointerSize);
|
|
PointerType* EnvpPointers = reinterpret_cast<PointerType*>(StackPointer + PointerSize + Args.size() * PointerSize + PointerSize);
|
|
AuxType* AuxVPointers = reinterpret_cast<AuxType*>(StackPointer + AuxVOffset);
|
|
|
|
// Arguments memory lives after everything else
|
|
uint8_t* ArgumentBackingBase = reinterpret_cast<uint8_t*>(StackPointer + ArgumentOffset);
|
|
uint8_t* EnvpBackingBase = reinterpret_cast<uint8_t*>(StackPointer + EnvpOffset);
|
|
PointerType ArgumentBackingBaseGuest = StackPointer + ArgumentOffset;
|
|
PointerType EnvpBackingBaseGuest = StackPointer + EnvpOffset;
|
|
|
|
*reinterpret_cast<PointerType*>(StackPointer + 0) = Args.size();
|
|
PadPointers[0] = 0;
|
|
|
|
// If we don't have any, just make sure the first is nullptr
|
|
EnvpPointers[0] = 0;
|
|
|
|
uint64_t CurrentOffset = 0;
|
|
for (size_t i = 0; i < Args.size(); ++i) {
|
|
size_t ArgSize = Args[i].size();
|
|
// Set the pointer to this argument
|
|
ArgumentPointers[i] = ArgumentBackingBaseGuest + CurrentOffset;
|
|
if (ArgSize > 0) {
|
|
// Copy the string in to the final location
|
|
memcpy(reinterpret_cast<void*>(ArgumentBackingBase + CurrentOffset), Args[i].data(), ArgSize);
|
|
}
|
|
|
|
// Set the null terminator for the string
|
|
*reinterpret_cast<uint8_t*>(ArgumentBackingBase + CurrentOffset + ArgSize) = 0;
|
|
|
|
CurrentOffset += ArgSize + 1;
|
|
}
|
|
|
|
CurrentOffset = 0;
|
|
for (size_t i = 0; i < EnvironmentVariables.size(); ++i) {
|
|
size_t EnvpSize = EnvironmentVariables[i].size();
|
|
// Set the pointer to this argument
|
|
EnvpPointers[i] = EnvpBackingBaseGuest + CurrentOffset;
|
|
|
|
// Copy the string in to the final location
|
|
if (EnvpSize) {
|
|
memcpy(reinterpret_cast<void*>(EnvpBackingBase + CurrentOffset), EnvironmentVariables[i].data(), EnvpSize);
|
|
}
|
|
|
|
// Set the null terminator for the string
|
|
*reinterpret_cast<uint8_t*>(EnvpBackingBase + CurrentOffset + EnvpSize) = 0;
|
|
|
|
CurrentOffset += EnvpSize + 1;
|
|
}
|
|
|
|
// Last envp needs to be nullptr
|
|
EnvpPointers[EnvironmentVariables.size()] = 0;
|
|
|
|
for (size_t i = 0; const auto& Variable : AuxVariables) {
|
|
AuxVPointers[i].key = Variable.key;
|
|
AuxVPointers[i].val = Variable.val;
|
|
++i;
|
|
}
|
|
|
|
*AuxTabBase = reinterpret_cast<uint64_t>(AuxVPointers);
|
|
*AuxTabSize = sizeof(AuxType) * AuxVariables.size();
|
|
}
|
|
|
|
// Get the current memory map from /proc/self/stat
|
|
static bool GetCurrentMap(struct prctl_mm_map& map) {
|
|
|
|
// /proc/self/stat has 52 fields of at most 20 digits each (UINT64_MAX).
|
|
// 52*20 = 1040, so 2048 is a conservative upper bound
|
|
char stat_buffer[2048];
|
|
ssize_t bytes_read = FEXCore::FileLoading::LoadFileToBuffer("/proc/self/stat", stat_buffer);
|
|
|
|
// Ensure we don't read past the end into garbage data
|
|
stat_buffer[std::clamp(bytes_read, 0L, static_cast<ssize_t>(sizeof(stat_buffer)) - 1)] = '\0';
|
|
|
|
// See man proc_pid_stat
|
|
int items_read = sscanf(stat_buffer,
|
|
"%*d %*s %*c %*d %*d " // 1 to 5
|
|
"%*d %*d %*d %*u %*u " // 6 to 10
|
|
"%*u %*u %*u %*u %*u " // 11 to 15
|
|
"%*d %*d %*d %*d %*d " // 16 to 20
|
|
"%*d %*u %*u %*d %*u " // 21 to 25
|
|
"%llu %llu %llu %*u %*u " // 26 to 30
|
|
"%*u %*u %*u %*u %*u " // 31 to 35
|
|
"%*u %*u %*d %*d %*u " // 36 to 40
|
|
"%*u %*u %*u %*d %llu " // 40 to 45
|
|
"%llu %llu %llu %llu %llu " // 46 to 50
|
|
"%llu", // 51
|
|
&map.start_code, &map.end_code, &map.start_stack, &map.start_data, &map.end_data, &map.start_brk,
|
|
&map.arg_start, &map.arg_end, &map.env_start, &map.env_end);
|
|
|
|
if (items_read != 10) {
|
|
return false;
|
|
}
|
|
|
|
map.brk = reinterpret_cast<uint64_t>(sbrk(0));
|
|
|
|
// The kernel will leave these values unchanged, see implementation in sys.c
|
|
map.auxv = NULL;
|
|
map.auxv_size = 0;
|
|
map.exe_fd = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Point the OS to our new stack's argument data
|
|
void RemapArgumentData(uintptr_t NewArgStart, uint64_t ArgSize) {
|
|
struct prctl_mm_map map {};
|
|
if (GetCurrentMap(map)) {
|
|
map.arg_start = NewArgStart;
|
|
map.arg_end = NewArgStart + ArgSize;
|
|
|
|
int r = prctl(PR_SET_MM, PR_SET_MM_MAP, &map, sizeof(map), 0L);
|
|
if (r != 0) {
|
|
LogMan::Msg::EFmt("Failed to remap /proc/pid/cmdline data (prctl failed: result {}, errno {})", r, errno);
|
|
}
|
|
} else {
|
|
LogMan::Msg::EFmt("Failed to remap /proc/pid/cmdline data (GetCurrentMap failed)");
|
|
}
|
|
}
|
|
|
|
// Setups the stack initial data (argv, envp, auxv)
|
|
void SetupStack() {
|
|
StackPointer += StackSize();
|
|
// Set up our initial CPU state
|
|
uint64_t SizeOfPointer = Is64BitMode() ? 8 : 4;
|
|
|
|
uint64_t TotalArgumentMemSize {};
|
|
|
|
TotalArgumentMemSize += SizeOfPointer; // Argument counter size
|
|
TotalArgumentMemSize += SizeOfPointer * ApplicationArgs.size(); // Pointers to strings
|
|
TotalArgumentMemSize += SizeOfPointer; // Padding for something
|
|
TotalArgumentMemSize += SizeOfPointer * EnvironmentVariables.size(); // Argument location for envp
|
|
TotalArgumentMemSize += SizeOfPointer; // envp nullptr ender
|
|
|
|
uint64_t AuxVOffset = TotalArgumentMemSize;
|
|
if (SizeOfPointer == 8) {
|
|
TotalArgumentMemSize += sizeof(auxv_t) * AuxVariables.size();
|
|
} else {
|
|
TotalArgumentMemSize += sizeof(auxv32_t) * AuxVariables.size();
|
|
}
|
|
|
|
ArgumentOffset = TotalArgumentMemSize;
|
|
TotalArgumentMemSize += ArgumentBackingSize;
|
|
|
|
uint64_t EnvpOffset = TotalArgumentMemSize;
|
|
TotalArgumentMemSize += EnvironmentBackingSize;
|
|
|
|
uint64_t PlatformNameLocation = TotalArgumentMemSize;
|
|
TotalArgumentMemSize += platform_string_max_size;
|
|
|
|
uint64_t ExecFNLocation = TotalArgumentMemSize;
|
|
TotalArgumentMemSize += ApplicationArgs[0].size() + 1;
|
|
|
|
// Align the argument block to 16 bytes to keep the stack aligned
|
|
TotalArgumentMemSize = FEXCore::AlignUp(TotalArgumentMemSize, 16);
|
|
|
|
// Random number location
|
|
uint64_t RandomNumberLocation = TotalArgumentMemSize;
|
|
TotalArgumentMemSize += 16;
|
|
|
|
// Offset the stack by how much memory we need
|
|
StackPointer -= TotalArgumentMemSize;
|
|
|
|
// Setup our AUXP values that need memory now that the stack is setup
|
|
AuxPlatform->val = StackPointer + PlatformNameLocation;
|
|
char* PlatformLoc = reinterpret_cast<char*>(AuxPlatform->val);
|
|
memset(PlatformLoc, 0, platform_string_max_size);
|
|
if (Is64BitMode()) {
|
|
strncpy(PlatformLoc, platform_name_x86_64.data(), platform_string_max_size);
|
|
} else {
|
|
strncpy(PlatformLoc, platform_name_i686.data(), platform_string_max_size);
|
|
}
|
|
|
|
// Random value is always 128bits
|
|
AuxRandom->val = StackPointer + RandomNumberLocation;
|
|
uint64_t* RandomLoc = reinterpret_cast<uint64_t*>(AuxRandom->val);
|
|
uint64_t* HostRandom = reinterpret_cast<uint64_t*>(getauxval(AT_RANDOM));
|
|
if (HostRandom) {
|
|
// Pass through the host's random values
|
|
RandomLoc[0] = HostRandom[0];
|
|
RandomLoc[1] = HostRandom[1];
|
|
} else {
|
|
// Nothing provided from the kernel, generate our own random values.
|
|
if (!GetRandom(&RandomLoc[0], sizeof(uint64_t) * 2)) {
|
|
// getrandom failed for some reason.
|
|
RandomLoc[0] = 0;
|
|
RandomLoc[1] = 0;
|
|
LogMan::Msg::EFmt("RNG failed. AT_RANDOM will not be random.");
|
|
}
|
|
}
|
|
|
|
// Setup ExecFN aux
|
|
AuxExecFN->val = StackPointer + ExecFNLocation;
|
|
const auto InvocationName = reinterpret_cast<char*>(AuxExecFN->val);
|
|
strncpy(InvocationName, ApplicationArgs[0].c_str(), ApplicationArgs[0].size() + 1);
|
|
|
|
// Stack setup
|
|
// [0, 8): Argument Count
|
|
// [8, 16): Argument Pointer 0
|
|
// [16, 24): Argument Pointer 1
|
|
// ....
|
|
// [Pad1, +8): Some Pointer
|
|
// [envp, +8): envp pointer
|
|
// [Pad2End, +8): Argument String 0
|
|
// [+8, +8): String 1
|
|
// ...
|
|
// [argvend, +8): envp[0]
|
|
// ...
|
|
// [envpend, +8): nullptr
|
|
|
|
if (SizeOfPointer == 8) {
|
|
SetupPointers<uint64_t, auxv_t, 8>(StackPointer, AuxVOffset, ArgumentOffset, EnvpOffset, ApplicationArgs, EnvironmentVariables,
|
|
AuxVariables, &AuxTabBase, &AuxTabSize);
|
|
} else {
|
|
SetupPointers<uint32_t, auxv32_t, 4>(StackPointer, AuxVOffset, ArgumentOffset, EnvpOffset, ApplicationArgs, EnvironmentVariables,
|
|
AuxVariables, &AuxTabBase, &AuxTabSize);
|
|
}
|
|
|
|
RemapArgumentData(StackPointer + ArgumentOffset, ArgumentBackingSize);
|
|
#if defined(HAS_PROGRAM_INVOCATION_NAME) && HAS_PROGRAM_INVOCATION_NAME
|
|
// Set the glibc invocation names to the process name.
|
|
// Mesa uses this to determine application profiles.
|
|
// Necessary when thunking is enabled otherwise mesa would only see FEX.
|
|
|
|
std::string_view INV = std::string_view(InvocationName, ApplicationArgs[0].size());
|
|
auto short_name = InvocationName;
|
|
auto iter = INV.rfind('/');
|
|
if (iter != INV.npos) {
|
|
short_name = &InvocationName[iter + 1];
|
|
}
|
|
|
|
program_invocation_name = InvocationName;
|
|
program_invocation_short_name = short_name;
|
|
#endif
|
|
}
|
|
|
|
fextl::vector<const char*> GetExecveArguments() const override {
|
|
return LoaderArgs;
|
|
}
|
|
|
|
AuxvResult GetAuxv() const override {
|
|
return {
|
|
.address = AuxTabBase,
|
|
.size = AuxTabSize,
|
|
};
|
|
}
|
|
|
|
uint64_t GetBaseOffset() const override {
|
|
return BaseOffset;
|
|
}
|
|
|
|
uint64_t GetMainElfBase() const {
|
|
return MainElfBase;
|
|
}
|
|
|
|
bool Is64BitMode() const {
|
|
return MainElf.type == ::ELFLoader::ELFContainer::TYPE_X86_64;
|
|
}
|
|
|
|
::ELFLoader::ELFContainer::BRKInfo GetBRKInfo() {
|
|
return ::ELFLoader::ELFContainer::BRKInfo {BrkStart, BRK_SIZE};
|
|
}
|
|
|
|
bool ELFWasLoaded() {
|
|
return ElfValid;
|
|
}
|
|
|
|
void SetVDSOBase(void* Base) {
|
|
VDSOBase = Base;
|
|
}
|
|
|
|
void CalculateHWCaps(FEXCore::Context::Context* ctx) {
|
|
// HWCAP is just CPUID function 0x1, the EDX result
|
|
auto res_1 = ctx->RunCPUIDFunction(1, 0);
|
|
auto res_7 = ctx->RunCPUIDFunction(7, 0);
|
|
|
|
HWCap = res_1.edx;
|
|
|
|
// HWCAP2 is as follows:
|
|
// Bits:
|
|
// 0 - MONITOR/MWAIT available in CPL3
|
|
// 1 - FSGSBASE instructions available in CPL3
|
|
HWCap2 = (res_7.ebx & 1) ? (1U << 1) : 0; // FSGSBase is exposed if CPUID_7_ebx[0] is set.
|
|
|
|
// We need to know if we support AVX for AT_MINSIGSTKSZ
|
|
SupportsAVX = !!(res_1.ecx & (1U << 28));
|
|
}
|
|
|
|
uint64_t CalculateSignalStackSize() const {
|
|
// We must calculate the required signal stack size that the "kernel" consumes.
|
|
// For FEX this means the amount of state we store in to the guest stack, not including the amount
|
|
// that FEX stores in to the host stack as well.
|
|
//
|
|
// This needs to match what we do in FEXCore's dispatcher (Which should at some point be moved to the frontend).
|
|
//
|
|
// This roughly means that we need to calculate the combined size of:
|
|
// - xstate or _libc_fstate depending on AVX support
|
|
// - ucontext_t
|
|
// - siginfo_t
|
|
// Size of state requiring to be stored is different between 32-bit and 64-bit.
|
|
|
|
uint64_t Result {};
|
|
if (Is64BitMode()) {
|
|
Result += sizeof(FEXCore::x86_64::ucontext_t);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86_64::ucontext_t));
|
|
if (SupportsAVX) {
|
|
Result += sizeof(FEXCore::x86_64::xstate);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86_64::xstate));
|
|
} else {
|
|
Result += sizeof(FEXCore::x86_64::_libc_fpstate);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86_64::_libc_fpstate));
|
|
}
|
|
|
|
Result += sizeof(siginfo_t);
|
|
Result = FEXCore::AlignUp(Result, alignof(siginfo_t));
|
|
} else {
|
|
Result += sizeof(FEXCore::x86::ucontext_t);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::ucontext_t));
|
|
if (SupportsAVX) {
|
|
Result += sizeof(FEXCore::x86::xstate);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::xstate));
|
|
} else {
|
|
Result += sizeof(FEXCore::x86::_libc_fpstate);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::_libc_fpstate));
|
|
}
|
|
|
|
Result += sizeof(FEXCore::x86::siginfo_t);
|
|
Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::siginfo_t));
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
constexpr static uint64_t BRK_SIZE = 8 * 1024 * 1024;
|
|
constexpr static uint64_t STACK_SIZE = 8 * 1024 * 1024;
|
|
constexpr static uint64_t FULL_STACK_SIZE = 128 * 1024 * 1024;
|
|
|
|
fextl::vector<fextl::string> EnvironmentVariables;
|
|
fextl::vector<const char*> LoaderArgs;
|
|
|
|
fextl::list<auxv_t> AuxVariables;
|
|
uint64_t AuxTabBase {}, AuxTabSize {};
|
|
uint64_t ArgumentBackingSize {};
|
|
uint64_t ArgumentOffset {};
|
|
uint64_t EnvironmentBackingSize {};
|
|
uint64_t BaseOffset {};
|
|
void* VDSOBase {};
|
|
uint64_t HWCap {};
|
|
uint64_t HWCap2 {};
|
|
bool SupportsAVX {};
|
|
|
|
auxv_t* AuxRandom {};
|
|
auxv_t* AuxPlatform {};
|
|
auxv_t* AuxExecFN {};
|
|
|
|
static constexpr std::string_view platform_name_x86_64 = "x86_64";
|
|
static constexpr std::string_view platform_name_i686 = "i686";
|
|
// Need to include null character.
|
|
static constexpr size_t platform_string_max_size = std::max(platform_name_x86_64.size(), platform_name_i686.size()) + 1;
|
|
|
|
FEX_CONFIG_OPT(AdditionalArguments, ADDITIONALARGUMENTS);
|
|
FEX_CONFIG_OPT(InjectLibSegFault, INJECTLIBSEGFAULT);
|
|
};
|