Files
FEX-Emu--FEX/Source/Tests/LinuxSyscalls/SyscallsSMCTracking.cpp
T
Ryan Houdek 35af4bd42a FEXCore: Removes C wrapper interface
This has been a long time coming. The C interface has been a thorn in
our side for no reason for a long time.

The purpose of this step is to remove the C interface without changing
behaviour as much as possible. This means that with this commit there
are still some bad practices but the remaining issues will be solved
with followup PRs.

Primarily, we still have a `DestroyContext(CTX)` static function which calls
the Context implementation's `DestroyContext` and does a raw C++ delete.

Follow up PR will remove that, but I didn't want to touch it yet since
it'll require checking to ensure the unique_ptr changes play nice with
our allocator hooking. Which this is already a huge PR without trying to
change behaviour.
2023-02-19 11:59:11 -08:00

366 lines
12 KiB
C++

/*
$info$
category: LinuxSyscalls ~ Linux syscall emulation, marshaling and passthrough
tags: LinuxSyscalls|common
desc: SMC/MMan Tracking
$end_info$
*/
#include "Common/FDUtils.h"
#include <filesystem>
#include <sys/shm.h>
#include <sys/mman.h>
#include "Tests/LinuxSyscalls/Syscalls.h"
#include <FEXHeaderUtils/TypeDefines.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/MathUtils.h>
namespace FEX::HLE {
/// Helpers ///
auto SyscallHandler::VMAProt::fromProt (int Prot) -> VMAProt {
return VMAProt {
.Readable = (Prot & PROT_READ) != 0,
.Writable = (Prot & PROT_WRITE) != 0,
.Executable = (Prot & PROT_EXEC) != 0,
};
}
auto SyscallHandler::VMAProt::fromSHM (int SHMFlg) -> VMAProt {
return VMAProt {
.Readable = true,
.Writable = SHMFlg & SHM_RDONLY ? false : true,
.Executable = false,
};
}
auto SyscallHandler::VMAFlags::fromFlags(int Flags) -> VMAFlags {
return VMAFlags {
.Shared = (Flags & MAP_SHARED) != 0, // also includes MAP_SHARED_VALIDATE
};
}
// SMC interactions
bool SyscallHandler::HandleSegfault(FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) {
auto CTX = Thread->CTX;
const auto FaultAddress = (uintptr_t)((siginfo_t *)info)->si_addr;
{
FHU::ScopedSignalMaskWithSharedLock lk(_SyscallHandler->VMATracking.Mutex);
auto VMATracking = &_SyscallHandler->VMATracking;
// If the write spans two pages, they will be flushed one at a time (generating two faults)
auto Entry = VMATracking->LookupVMAUnsafe(FaultAddress);
// If an untracked address, or the mapping wasn't writable, it can't be handled here
if (Entry == VMATracking->VMAs.end() || !Entry->second.Prot.Writable) {
return false;
}
auto FaultBase = FEXCore::AlignDown(FaultAddress, FHU::FEX_PAGE_SIZE);
if (Entry->second.Flags.Shared) {
LOGMAN_THROW_A_FMT(Entry->second.Resource, "VMA tracking error");
auto Offset = FaultBase - Entry->first + Entry->second.Offset;
auto VMA = Entry->second.Resource->FirstVMA;
LOGMAN_THROW_AA_FMT(VMA, "VMA tracking error");
// Flush all mirrors, remap the page writable as needed
do {
if (VMA->Offset <= Offset && (VMA->Offset + VMA->Length) > Offset) {
auto FaultBaseMirrored = Offset - VMA->Offset + VMA->Base;
if (VMA->Prot.Writable) {
CTX->InvalidateGuestCodeRange(FaultBaseMirrored, FHU::FEX_PAGE_SIZE, [](uintptr_t Start, uintptr_t Length) {
auto rv = mprotect((void *)Start, Length, PROT_READ | PROT_WRITE);
LogMan::Throw::AAFmt(rv == 0, "mprotect({}, {}) failed", Start, Length);
});
} else {
CTX->InvalidateGuestCodeRange(FaultBaseMirrored, FHU::FEX_PAGE_SIZE);
}
}
} while ((VMA = VMA->ResourceNextVMA));
} else {
CTX->InvalidateGuestCodeRange(FaultBase, FHU::FEX_PAGE_SIZE, [](uintptr_t Start, uintptr_t Length) {
auto rv = mprotect((void *)Start, Length, PROT_READ | PROT_WRITE);
LogMan::Throw::AAFmt(rv == 0, "mprotect({}, {}) failed", Start, Length);
});
}
return true;
}
}
void SyscallHandler::MarkGuestExecutableRange(uint64_t Start, uint64_t Length) {
const auto Base = Start & FHU::FEX_PAGE_MASK;
const auto Top = FEXCore::AlignUp(Start + Length, FHU::FEX_PAGE_SIZE);
{
if (SMCChecks != FEXCore::Config::CONFIG_SMC_MTRACK) {
return;
}
FHU::ScopedSignalMaskWithSharedLock lk(VMATracking.Mutex);
// Find the first mapping at or after the range ends, or ::end().
// Top points to the address after the end of the range
auto Mapping = VMATracking.VMAs.lower_bound(Top);
while (Mapping != VMATracking.VMAs.begin()) {
Mapping--;
const auto MapBase = Mapping->first;
const auto MapTop = MapBase + Mapping->second.Length;
if (MapTop <= Base) {
// Mapping ends before the Range start, exit
break;
} else {
const auto ProtectBase = std::max(MapBase, Base);
const auto ProtectSize = std::min(MapTop, Top) - ProtectBase;
if (Mapping->second.Flags.Shared) {
LOGMAN_THROW_A_FMT(Mapping->second.Resource, "VMA tracking error");
const auto OffsetBase = ProtectBase - Mapping->first + Mapping->second.Offset;
const auto OffsetTop = OffsetBase + ProtectSize;
auto VMA = Mapping->second.Resource->FirstVMA;
LOGMAN_THROW_AA_FMT(VMA, "VMA tracking error");
do {
auto VMAOffsetBase = VMA->Offset;
auto VMAOffsetTop = VMA->Offset + VMA->Length;
auto VMABase = VMA->Base;
if (VMA->Prot.Writable && VMAOffsetBase < OffsetTop && VMAOffsetTop > OffsetBase) {
const auto MirroredBase = std::max(VMAOffsetBase, OffsetBase);
const auto MirroredSize = std::min(OffsetTop, VMAOffsetTop) - MirroredBase;
auto rv = mprotect((void *)(MirroredBase - VMAOffsetBase + VMABase), MirroredSize, PROT_READ);
LogMan::Throw::AAFmt(rv == 0, "mprotect({}, {}) failed", MirroredBase, MirroredSize);
}
} while ((VMA = VMA->ResourceNextVMA));
} else if (Mapping->second.Prot.Writable) {
int rv = mprotect((void *)ProtectBase, ProtectSize, PROT_READ);
LogMan::Throw::AAFmt(rv == 0, "mprotect({}, {}) failed", ProtectBase, ProtectSize);
}
}
}
}
}
// Used for AOT
FEXCore::HLE::AOTIRCacheEntryLookupResult SyscallHandler::LookupAOTIRCacheEntry(uint64_t GuestAddr) {
FHU::ScopedSignalMaskWithSharedLock lk(_SyscallHandler->VMATracking.Mutex);
auto rv = FEXCore::HLE::AOTIRCacheEntryLookupResult(nullptr, 0, std::move(lk));
// Get the first mapping after GuestAddr, or end
// GuestAddr is inclusive
// If the write spans two pages, they will be flushed one at a time (generating two faults)
auto Entry = _SyscallHandler->VMATracking.LookupVMAUnsafe(GuestAddr);
if (Entry != _SyscallHandler->VMATracking.VMAs.end()) {
rv.Entry = Entry->second.Resource ? Entry->second.Resource->AOTIRCacheEntry : nullptr;
rv.VAFileStart = Entry->second.Base - Entry->second.Offset;
}
return rv;
}
// MMan Tracking
void SyscallHandler::TrackMmap(uintptr_t Base, uintptr_t Size, int Prot, int Flags, int fd, off_t Offset) {
Size = FEXCore::AlignUp(Size, FHU::FEX_PAGE_SIZE);
if (Flags & MAP_SHARED) {
CTX->MarkMemoryShared();
}
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
static uint64_t AnonSharedId = 1;
MappedResource *Resource = nullptr;
if (!(Flags & MAP_ANONYMOUS)) {
struct stat64 buf;
fstat64(fd, &buf);
MRID mrid {buf.st_dev, buf.st_ino};
auto filename = FEX::get_fdpath(fd);
if (filename.has_value()) {
auto [Iter, Inserted] = VMATracking.MappedResources.emplace(mrid, MappedResource {nullptr, nullptr, 0});
Resource = &Iter->second;
if (Inserted) {
Resource->AOTIRCacheEntry = CTX->LoadAOTIRCacheEntry(filename.value());
Resource->Iterator = Iter;
}
}
} else if (Flags & MAP_SHARED) {
MRID mrid{SpecialDev::Anon, AnonSharedId++};
auto [Iter, Inserted] = VMATracking.MappedResources.emplace(mrid, MappedResource{nullptr, nullptr, 0});
LOGMAN_THROW_AA_FMT(Inserted == true, "VMA tracking error");
Resource = &Iter->second;
Resource->Iterator = Iter;
} else {
Resource = nullptr;
}
VMATracking.SetUnsafe(CTX, Resource, Base, Offset, Size, VMAFlags::fromFlags(Flags), VMAProt::fromProt(Prot));
}
if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) {
CTX->InvalidateGuestCodeRange((uintptr_t)Base, Size);
}
}
void SyscallHandler::TrackMunmap(uintptr_t Base, uintptr_t Size) {
Size = FEXCore::AlignUp(Size, FHU::FEX_PAGE_SIZE);
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
VMATracking.ClearUnsafe(CTX, Base, Size);
}
if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) {
CTX->InvalidateGuestCodeRange((uintptr_t)Base, Size);
}
}
void SyscallHandler::TrackMprotect(uintptr_t Base, uintptr_t Size, int Prot) {
Size = FEXCore::AlignUp(Size, FHU::FEX_PAGE_SIZE);
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
VMATracking.ChangeUnsafe(Base, Size, VMAProt::fromProt(Prot));
}
if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) {
CTX->InvalidateGuestCodeRange(Base, Size);
}
}
void SyscallHandler::TrackMremap(uintptr_t OldAddress, size_t OldSize, size_t NewSize, int flags, uintptr_t NewAddress) {
OldSize = FEXCore::AlignUp(OldSize, FHU::FEX_PAGE_SIZE);
NewSize = FEXCore::AlignUp(NewSize, FHU::FEX_PAGE_SIZE);
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
const auto OldVMA = VMATracking.LookupVMAUnsafe(OldAddress);
const auto OldResource = OldVMA->second.Resource;
const auto OldOffset = OldVMA->second.Offset + OldAddress - OldVMA->first;
const auto OldFlags = OldVMA->second.Flags;
const auto OldProt = OldVMA->second.Prot;
LOGMAN_THROW_A_FMT(OldVMA != VMATracking.VMAs.end(), "VMA Tracking corruption");
if (OldSize == 0) {
// Mirror existing mapping
// must be a shared mapping
LOGMAN_THROW_AA_FMT(OldResource != nullptr, "VMA Tracking error");
LOGMAN_THROW_AA_FMT(OldFlags.Shared, "VMA Tracking error");
VMATracking.SetUnsafe(CTX, OldResource, NewAddress, OldOffset, NewSize, OldFlags, OldProt);
} else {
// MREMAP_DONTUNMAP is kernel 5.7+
#ifdef MREMAP_DONTUNMAP
if (!(flags & MREMAP_DONTUNMAP))
#endif
{
VMATracking.ClearUnsafe(CTX, OldAddress, OldSize, OldResource);
}
// Make anonymous mapping
VMATracking.SetUnsafe(CTX, OldResource, NewAddress, OldOffset, NewSize, OldFlags, OldProt);
}
}
if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) {
if (OldAddress != NewAddress) {
if (OldSize != 0) {
// This also handles the MREMAP_DONTUNMAP case
CTX->InvalidateGuestCodeRange(OldAddress, OldSize);
}
} else {
// If mapping shrunk, flush the unmapped region
if (OldSize > NewSize) {
CTX->InvalidateGuestCodeRange(OldAddress + NewSize, OldSize - NewSize);
}
}
}
}
void SyscallHandler::TrackShmat(int shmid, uintptr_t Base, int shmflg) {
CTX->MarkMemoryShared();
shmid_ds stat;
auto res = shmctl(shmid, IPC_STAT, &stat);
LOGMAN_THROW_AA_FMT(res != -1, "shmctl IPC_STAT failed");
uint64_t Length = stat.shm_segsz;
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
// TODO
MRID mrid{SpecialDev::SHM, static_cast<uint64_t>(shmid)};
auto ResourceInserted = VMATracking.MappedResources.insert({mrid, {nullptr, nullptr, Length}});
auto Resource = &ResourceInserted.first->second;
if (ResourceInserted.second) {
Resource->Iterator = ResourceInserted.first;
}
VMATracking.SetUnsafe(CTX, Resource, Base, 0, Length, VMAFlags::fromFlags(MAP_SHARED),
VMAProt::fromProt((shmflg & SHM_RDONLY) ? PROT_READ : (PROT_READ | PROT_WRITE))
);
}
if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) {
CTX->InvalidateGuestCodeRange(Base, Length);
}
}
void SyscallHandler::TrackShmdt(uintptr_t Base) {
uintptr_t Length = 0;
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
Length = VMATracking.ClearShmUnsafe(CTX, Base);
}
if (SMCChecks != FEXCore::Config::CONFIG_SMC_NONE) {
// This might over flush if the shm has holes in it
CTX->InvalidateGuestCodeRange(Base, Length);
}
}
void SyscallHandler::TrackMadvise(uintptr_t Base, uintptr_t Size, int advice) {
Size = FEXCore::AlignUp(Size, FHU::FEX_PAGE_SIZE);
{
FHU::ScopedSignalMaskWithUniqueLock lk(_SyscallHandler->VMATracking.Mutex);
// TODO
}
}
}