Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Thread.cpp
T
Ryan Houdek 4516e4e9f6 LinuxSyscalls: Support modify_ldt on x64
This nearly gets FEX's TestHarnessRunner to be self-hosting inside of
FEX. The only thing blocking it currently is that our SBRK emulation
reserves the whole region, when it should be "soft-reserved" and mmap
with MAP_FIXED_NOREPLACE can override it. Plus an assert in
OpcodeDispatcher preventing any 32-bit code from running from a 64-bit
process.

In the most simple terms, gdt and ldt are setup to be unique per thread,
and modify_ldt then modifies that thread's ldt entry. On thread
creation, these values get inherited as a copy.

This allows installation of 32-bit code entries, which with the previous
PRs merged allows the code to attempt jumping to that 32-bit code entry.
It then will immediately explode with an assert in our OpcodeDispatcher.
We can't allow 32-bit code jumping yet until our OpDispatcher/X86Tables
allows runtime selection of 64-bit and 32-bit code entries which is
still a ways away.

With the assert removed and the SBRK code handling hacked out,
/technically/ the TestHarnessRunner can run some code, albeit anything
that changes behaviour between bitness is completely incorrect.
2025-08-20 13:22:56 -07:00

351 lines
12 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: LinuxSyscalls|syscalls-x86-64
$end_info$
*/
#include "LinuxSyscalls/SignalDelegator.h"
#include "LinuxSyscalls/Syscalls.h"
#include "LinuxSyscalls/x64/Syscalls.h"
#include "LinuxSyscalls/x64/Thread.h"
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/IR/IR.h>
#include <FEXCore/fextl/vector.h>
#include <sched.h>
#include <signal.h>
#include <stddef.h>
#include <syscall.h>
#include <stdint.h>
#include <unistd.h>
namespace FEX::HLE {
uint64_t SyscallHandler::read_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (!Thread->ldt_entries) {
return 0;
}
bytecount = std::min(bytecount, MAX_LDT_ENTRIES * LDT_ENTRY_SIZE);
const auto EntriesToCopySize = std::min(bytecount, Thread->ldt_entry_count * LDT_ENTRY_SIZE);
if (FaultSafeUserMemAccess::CopyToUser(ptr, Thread->ldt_entries, EntriesToCopySize) != EntriesToCopySize) {
return -EFAULT;
}
// Quirk that if the number of bytes that the user is asking for is larger than the amount we have, then zero the remaining memory.
// This means the guest can't ever know the actual size of the LDT.
size_t RemainingSize = bytecount - EntriesToCopySize;
if (RemainingSize) {
void *remaining = alloca(RemainingSize);
memset(remaining, 0, RemainingSize);
if (FaultSafeUserMemAccess::CopyToUser(reinterpret_cast<uint8_t*>(ptr) + EntriesToCopySize, remaining, RemainingSize) != RemainingSize) {
return -EFAULT;
}
}
// Return the combined size of ldt entries and zero initialized range.
// I don't make the rules, it's just the weirdness that the kernel does.
return bytecount;
}
static uint64_t read_default_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount) {
// This is some weird old legacy thing. Just returns zeroes up to 128-bytes.
uint8_t Data[128] {};
bytecount = std::min<uint64_t>(bytecount, sizeof(Data));
if (FaultSafeUserMemAccess::CopyToUser(ptr, Data, bytecount) != bytecount) {
return -EFAULT;
}
return bytecount;
}
uint64_t SyscallHandler::write_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount, bool legacy) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
struct user_desc_x64 {
uint32_t entry_number;
uint32_t base_addr;
uint32_t limit;
uint32_t seg_32bit : 1;
uint32_t contents : 2;
uint32_t read_exec_only : 1;
uint32_t limit_in_pages : 1;
uint32_t seg_not_present : 1;
uint32_t useable : 1;
uint32_t lm : 1;
};
static_assert(sizeof(user_desc_x64) == 16);
// `content` member variables.
constexpr static uint32_t MODIFY_LDT_CONTENTS_CONFORMING = 3;
if (bytecount != sizeof(user_desc_x64)) {
// Can only write a single ldt. Reject smaller and larger values.
return -EINVAL;
}
user_desc_x64 ldt_info {};
FEXCore::Core::CPUState::gdt_segment ldt {};
if (FaultSafeUserMemAccess::CopyFromUser(&ldt_info, ptr, sizeof(ldt_info)) == EFAULT) {
// Reject if we can't read it.
return -EFAULT;
}
if (ldt_info.entry_number > MAX_LDT_ENTRIES) {
return -EINVAL;
}
if (ldt_info.contents == MODIFY_LDT_CONTENTS_CONFORMING) {
// Conforming is mostly ignored.
// Legacy doesn't support it at all. Good.
if (legacy) return -EINVAL;
// Non-legacy ignores if only if the `seg_not_present` is set.
if (ldt_info.seg_not_present == 0) return -EINVAL;
}
auto is_empty = [](user_desc_x64 ldt_info, bool legacy) {
// Legacy empty is trivial.
const bool legacy_empty = legacy && ldt_info.base_addr == 0 && ldt_info.limit == 0;
if (legacy_empty) return true;
// Non-legacy is a bit more work.
return ldt_info.base_addr == 0 &&
ldt_info.limit == 0 &&
ldt_info.contents == 0 &&
ldt_info.read_exec_only == 1 &&
ldt_info.limit_in_pages == 0 &&
ldt_info.seg_not_present == 1 &&
ldt_info.useable == 0;
};
auto fill_ldt = [](FEXCore::Core::CPUState::gdt_segment &segment, user_desc_x64 ldt_info) {
FEXCore::Core::CPUState::SetGDTBase(&segment, ldt_info.base_addr);
FEXCore::Core::CPUState::SetGDTLimit(&segment, ldt_info.limit);
// Additional flags
// Type: bit [11:8]
// - bit[8] - Accessed
// - bit[9] - Readable
// - bit[10] - Conforming
// - bit[11]
// - 1 - Code
// - 0 - Data
segment.Type =
((ldt_info.read_exec_only ^ 1) << 1) | // Readable
(ldt_info.contents << 2) | // Code/Data+Conforming
1; // Accessed
// S: bit [12]
// - 0 (System descriptor)
// - 1 (User descriptor)
segment.S = 1;
// DPL: bit[14:13]
segment.DPL = 3;
// P: Present
segment.P = ldt_info.seg_not_present ^ 1;
// AVL: Available to software
segment.AVL = ldt_info.useable;
// L: Long-mode
// This doesn't allow setting 64-bit segments!
segment.L = 0;
// D: Default operand size
// - 0: 16-bit operand size
// - 1: 32-bit operand size
segment.D = ldt_info.seg_32bit;
// G: Granularity
segment.G = ldt_info.limit_in_pages;
};
if (is_empty(ldt_info, legacy)) {
// If the ldt_info is considered empty then this is a zeroing operation.
// Just use the zero ldt.
}
else {
// This syscall only allows installing 32-bit segments. If `seg_32bit` isn't set then
// it assumes a 16-bit segment!
if (!ldt_info.seg_32bit) {
return -EINVAL;
}
fill_ldt(ldt, ldt_info);
if (legacy) {
// Legacy always zeros this.
ldt.AVL = 0;
}
}
// Need to be careful with ldt replacement here to ensure it is atomically visible.
auto old_ldt = Thread->ldt_entries;
auto old_ldt_entries = Thread->ldt_entry_count;
const auto new_ldt_count = std::max<size_t>(old_ldt_entries, ldt_info.entry_number + 1);
const auto new_ldt_size = new_ldt_count * LDT_ENTRY_SIZE;
const auto new_ldt_entries = reinterpret_cast<FEXCore::Core::CPUState::gdt_segment*>(FEXCore::Allocator::mmap(nullptr, new_ldt_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
if (old_ldt) {
// Copy old entries if they existed.
memcpy(new_ldt_entries, old_ldt, old_ldt_entries * LDT_ENTRY_SIZE);
}
// Set new LDT.
new_ldt_entries[ldt_info.entry_number] = ldt;
// Set new LDT pointer.
Thread->ldt_entries = new_ldt_entries;
Thread->ldt_entry_count = new_ldt_count;
// Give the new LDT to CPUState.
Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = new_ldt_entries;
if (old_ldt) {
FEXCore::Allocator::munmap(old_ldt, old_ldt_entries * LDT_ENTRY_SIZE);
}
return 0;
}
}
namespace FEX::HLE::x64 {
uint64_t SetThreadArea(FEXCore::Core::CpuStateFrame* Frame, void* tls) {
Frame->State.fs_cached = reinterpret_cast<uint64_t>(tls);
return 0;
}
void AdjustRipForNewThread(FEXCore::Core::CpuStateFrame* Frame) {
Frame->State.rip += 2;
}
enum Modify_ldt_func : int32_t {
LDT_READ = 0,
LDT_WRITE_LEGACY = 1,
LDT_READ_DEFAULT = 2,
LDT_WRITE = 0x11,
};
void RegisterThread(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_X64(modify_ldt, [](FEXCore::Core::CpuStateFrame* Frame, int func, void* ptr, unsigned long bytecount) -> uint64_t {
switch (func) {
case Modify_ldt_func::LDT_READ: return FEX::HLE::_SyscallHandler->read_ldt(Frame, ptr, bytecount);
case Modify_ldt_func::LDT_WRITE_LEGACY: return FEX::HLE::_SyscallHandler->write_ldt(Frame, ptr, bytecount, true);
case Modify_ldt_func::LDT_READ_DEFAULT: return read_default_ldt(Frame, ptr, bytecount);
case Modify_ldt_func::LDT_WRITE: return FEX::HLE::_SyscallHandler->write_ldt(Frame, ptr, bytecount, false);
default: return -ENOSYS;
}
});
REGISTER_SYSCALL_IMPL_X64_FLAGS(
clone, SyscallFlags::DEFAULT,
([](FEXCore::Core::CpuStateFrame* Frame, uint32_t flags, void* stack, pid_t* parent_tid, pid_t* child_tid, void* tls) -> uint64_t {
// This is slightly different EFAULT behaviour, if child_tid or parent_tid is invalid then the kernel just doesn't write to the
// pointer. Still need to be EFAULT safe although.
if ((flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && child_tid) {
FaultSafeUserMemAccess::VerifyIsWritable(child_tid, sizeof(*child_tid));
}
if ((flags & CLONE_PARENT_SETTID) && parent_tid) {
FaultSafeUserMemAccess::VerifyIsWritable(parent_tid, sizeof(*parent_tid));
}
FEX::HLE::clone3_args args {
.Type = TypeOfClone::TYPE_CLONE2,
.args =
{
.flags = flags & ~CSIGNAL, // This no longer contains CSIGNAL
.pidfd = 0, // For clone, pidfd is duplicated here
.child_tid = reinterpret_cast<uint64_t>(child_tid),
.parent_tid = reinterpret_cast<uint64_t>(parent_tid),
.exit_signal = flags & CSIGNAL,
.stack = reinterpret_cast<uint64_t>(stack),
.stack_size = 0, // This syscall isn't able to see the stack size
.tls = reinterpret_cast<uint64_t>(tls),
.set_tid = 0, // This syscall isn't able to select TIDs
.set_tid_size = 0,
.cgroup = 0, // This syscall can't select cgroups
},
};
return CloneHandler(Frame, &args);
}));
REGISTER_SYSCALL_IMPL_X64(sigaltstack, [](FEXCore::Core::CpuStateFrame* Frame, const stack_t* ss, stack_t* old_ss) -> uint64_t {
FaultSafeUserMemAccess::VerifyIsReadableOrNull(ss, sizeof(*ss));
FaultSafeUserMemAccess::VerifyIsWritableOrNull(old_ss, sizeof(*old_ss));
return FEX::HLE::_SyscallHandler->GetSignalDelegator()->RegisterGuestSigAltStack(
FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame), ss, old_ss);
});
// launch a new process under fex
// currently does not propagate argv[0] correctly
REGISTER_SYSCALL_IMPL_X64_FLAGS(execve, SyscallFlags::DEFAULT,
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, char* const argv[], char* const envp[]) -> uint64_t {
fextl::vector<const char*> Args;
fextl::vector<const char*> Envp;
if (argv) {
for (int i = 0; argv[i]; i++) {
Args.push_back(argv[i]);
}
Args.push_back(nullptr);
}
if (envp) {
for (int i = 0; envp[i]; i++) {
Envp.push_back(envp[i]);
}
Envp.push_back(nullptr);
}
auto* const* ArgsPtr = argv ? const_cast<char* const*>(Args.data()) : nullptr;
auto* const* EnvpPtr = envp ? const_cast<char* const*>(Envp.data()) : nullptr;
FEX::HLE::ExecveAtArgs AtArgs = FEX::HLE::ExecveAtArgs::Empty();
return FEX::HLE::ExecveHandler(Frame, pathname, ArgsPtr, EnvpPtr, AtArgs);
});
REGISTER_SYSCALL_IMPL_X64_FLAGS(
execveat, SyscallFlags::DEFAULT,
([](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, char* const argv[], char* const envp[], int flags) -> uint64_t {
fextl::vector<const char*> Args;
fextl::vector<const char*> Envp;
if (argv) {
for (int i = 0; argv[i]; i++) {
Args.push_back(argv[i]);
}
Args.push_back(nullptr);
}
if (envp) {
for (int i = 0; envp[i]; i++) {
Envp.push_back(envp[i]);
}
Envp.push_back(nullptr);
}
FEX::HLE::ExecveAtArgs AtArgs {
.dirfd = dirfd,
.flags = flags,
};
auto* const* ArgsPtr = argv ? const_cast<char* const*>(Args.data()) : nullptr;
auto* const* EnvpPtr = envp ? const_cast<char* const*>(Envp.data()) : nullptr;
return FEX::HLE::ExecveHandler(Frame, pathname, ArgsPtr, EnvpPtr, AtArgs);
}));
}
} // namespace FEX::HLE::x64