Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/VDSO_Emulation.cpp
T
Ryan Houdek ad132267ec Linux/SMCTracking: Fixes nasty race condition causing invalid memory tracking
This has been a bug that we have technically lived with ever since SMC
tracking was introduced. The problem boils down to the fact that memory
management syscalls from multiple threads can race our SMC tracking.

This was only uncovered due to recent changes in the Steam client where
downloading games has more aggressively started reallocating memory.
This causes Steam to oversubscribe the CPU by a small margin, causing
threads to context switch more heavily during memory management.

The strace that finally managed to capture this:
```
41574 munmap(0xba84e000, 724992 <unfinished ...>
<...>
41227 mmap(NULL, 540672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -3, 0 <unfinished ...>
<...>
41574 <... munmap resumed>)             = 0
<...>
41227 <... mmap resumed>)               = 0xba87b000
```

While FEX's tracking linearly was:
```
mmap, 0xba87b000, 0x84000, 0x3, 0x22, 0xfffffffd, 0x0
munmap, 0xba84e000, 0xb1000
```

The way munmap and mmap perfectly interleave while getting context switched meant that the kernel's view of munmap then mmap didn't match our view of mmap completing first then munmap happening afterwards.
The kernel/strace is obviously the correct view in this instance.

This all comes down to how these threads are racing the VMA tracking
mutex after the syscall happens and not guaranteeing sequential
consistency that matches the kernel's view.

The only way to correct this sanely is to extend the locking period to
also encompass the syscalls getting executed. This is a bit tricky since
the VMA tracking needs to ensure that the lock is no longer held once
ThreadManager invalidation occurs so a callback to do the syscall
operation is about the only sane approach here. Luckily we now have
fextl::move_only_function.

Fixes consistent crashes with Steam game downloads (and maybe some
chromium crashes?)
2025-05-29 12:15:26 -07:00

822 lines
27 KiB
C++

// SPDX-License-Identifier: MIT
#include "VDSO_Emulation.h"
#include "FEXCore/IR/IR.h"
#include "LinuxSyscalls/x32/Types.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/Core/Context.h>
#include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/fextl/fmt.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <array>
#include <dlfcn.h>
#include <elf.h>
#include <fcntl.h>
#include <filesystem>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <unistd.h>
namespace FEX::VDSO {
VDSOSigReturn VDSOPointers {};
namespace VDSOHandlers {
using TimeType = decltype(::time)*;
using GetTimeOfDayType = decltype(::gettimeofday)*;
using ClockGetTimeType = decltype(::clock_gettime)*;
using ClockGetResType = decltype(::clock_getres)*;
using GetCPUType = decltype(FHU::Syscalls::getcpu)*;
using GetRandomType = ssize_t (*)(void*, size_t, uint32_t, void*, size_t);
TimeType TimePtr;
GetTimeOfDayType GetTimeOfDayPtr;
ClockGetTimeType ClockGetTimePtr;
ClockGetResType ClockGetResPtr;
GetCPUType GetCPUPtr;
GetRandomType GetRandomPtr;
} // namespace VDSOHandlers
using HandlerPtr = void (*)(void*);
namespace x64 {
static uint64_t SyscallRet(uint64_t Result) {
if (Result == -1) {
return -errno;
}
return Result;
}
// glibc handlers
namespace glibc {
static void time(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
time_t* a_0;
uint64_t rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
uint64_t Result = ::time(args->a_0);
args->rv = SyscallRet(Result);
}
static void gettimeofday(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
struct timeval* tv;
struct timezone* tz;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
int Result = ::gettimeofday(args->tv, args->tz);
args->rv = SyscallRet(Result);
}
static void clock_gettime(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
struct timespec* tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
int Result = ::clock_gettime(args->clk_id, args->tp);
args->rv = SyscallRet(Result);
}
static void clock_getres(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
struct timespec* tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
int Result = ::clock_getres(args->clk_id, args->tp);
args->rv = SyscallRet(Result);
}
static void getcpu(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
uint32_t* cpu;
uint32_t* node;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
int Result = FHU::Syscalls::getcpu(args->cpu, args->node);
args->rv = SyscallRet(Result);
}
static void getrandom(void* ArgsRV) {
struct vgetrandom_opaque_params {
uint32_t size_of_opaque_state;
uint32_t mmap_prot;
uint32_t mmap_flags;
uint32_t reserved[13];
};
static_assert(sizeof(vgetrandom_opaque_params) == sizeof(uint32_t[16]));
struct __attribute__((packed)) ArgsRV_t {
void* buffer;
size_t len;
uint32_t flags;
vgetrandom_opaque_params* opaque_state;
size_t opaque_len;
ssize_t rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
if (args->buffer == nullptr && args->len == 0 && args->flags == 0 && args->opaque_len == ~0ULL) [[unlikely]] {
// Special case querying for flags
// Since this is the syscall implementation, we need to return valid but unused data.
// This will cause glibc to allocate a page of memory, but it ends up being unused.
args->opaque_state->size_of_opaque_state = 4096;
args->opaque_state->mmap_prot = PROT_NONE;
args->opaque_state->mmap_flags = MAP_NORESERVE | MAP_ANONYMOUS | MAP_PRIVATE;
args->rv = 0;
return;
}
int Result = ::syscall(SYS_getrandom, args->buffer, args->len, args->flags);
args->rv = SyscallRet(Result);
}
} // namespace glibc
namespace VDSO {
// VDSO handlers
static void time(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
time_t* a_0;
uint64_t rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::TimePtr(args->a_0);
}
static void gettimeofday(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
struct timeval* tv;
struct timezone* tz;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::GetTimeOfDayPtr(args->tv, args->tz);
}
static void clock_gettime(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
struct timespec* tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::ClockGetTimePtr(args->clk_id, args->tp);
}
static void clock_getres(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
struct timespec* tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::ClockGetResPtr(args->clk_id, args->tp);
}
static void getcpu(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
uint32_t* cpu;
uint32_t* node;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::GetCPUPtr(args->cpu, args->node);
}
static void getrandom(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
void* buffer;
size_t len;
uint32_t flags;
void* opaque_state;
size_t opaque_len;
ssize_t rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::GetRandomPtr(args->buffer, args->len, args->flags, args->opaque_state, args->opaque_len);
}
} // namespace VDSO
HandlerPtr Handler_time = FEX::VDSO::x64::glibc::time;
HandlerPtr Handler_gettimeofday = FEX::VDSO::x64::glibc::gettimeofday;
HandlerPtr Handler_clock_gettime = FEX::VDSO::x64::glibc::clock_gettime;
HandlerPtr Handler_clock_getres = FEX::VDSO::x64::glibc::clock_getres;
HandlerPtr Handler_getcpu = FEX::VDSO::x64::glibc::getcpu;
HandlerPtr Handler_getrandom = FEX::VDSO::x64::glibc::getrandom;
} // namespace x64
namespace x32 {
namespace glibc {
static int SyscallRet(int Result) {
if (Result == -1) {
return -errno;
}
return Result;
}
// glibc handlers
static void time(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
HLE::x32::compat_ptr<FEX::HLE::x32::old_time32_t> a_0;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
time_t Host {};
int Result = ::time(&Host);
args->rv = SyscallRet(Result);
if (Result != -1 && args->a_0) {
*args->a_0 = Host;
}
}
static void gettimeofday(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
HLE::x32::compat_ptr<FEX::HLE::x32::timeval32> tv;
HLE::x32::compat_ptr<struct timezone> tz;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
struct timeval tv64 {};
struct timeval* tv_ptr {};
if (args->tv) {
tv_ptr = &tv64;
}
int Result = ::gettimeofday(tv_ptr, args->tz);
args->rv = SyscallRet(Result);
if (Result != -1 && args->tv) {
*args->tv = tv64;
}
}
static void clock_gettime(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
HLE::x32::compat_ptr<HLE::x32::timespec32> tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
struct timespec tp64 {};
int Result = ::clock_gettime(args->clk_id, &tp64);
args->rv = SyscallRet(Result);
if (Result != -1 && args->tp) {
*args->tp = tp64;
}
}
static void clock_gettime64(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
HLE::x32::compat_ptr<struct timespec> tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
int Result = ::clock_gettime(args->clk_id, args->tp);
args->rv = SyscallRet(Result);
}
static void clock_getres(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
HLE::x32::compat_ptr<HLE::x32::timespec32> tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
struct timespec tp64 {};
int Result = ::clock_getres(args->clk_id, &tp64);
args->rv = SyscallRet(Result);
if (Result != -1 && args->tp) {
*args->tp = tp64;
}
}
static void getcpu(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
HLE::x32::compat_ptr<uint32_t> cpu;
HLE::x32::compat_ptr<uint32_t> node;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
int Result = ::getcpu(args->cpu, args->node);
args->rv = SyscallRet(Result);
}
} // namespace glibc
namespace VDSO {
static bool SyscallErr(uint64_t Result) {
return Result >= -4095;
}
// VDSO handlers
static void time(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
HLE::x32::compat_ptr<FEX::HLE::x32::old_time32_t> a_0;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
time_t Host {};
uint64_t Result = VDSOHandlers::TimePtr(&Host);
args->rv = Result;
if (!SyscallErr(Result) && args->a_0) {
*args->a_0 = Host;
}
}
static void gettimeofday(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
HLE::x32::compat_ptr<FEX::HLE::x32::timeval32> tv;
HLE::x32::compat_ptr<struct timezone> tz;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
struct timeval tv64 {};
struct timeval* tv_ptr {};
if (args->tv) {
tv_ptr = &tv64;
}
uint64_t Result = VDSOHandlers::GetTimeOfDayPtr(tv_ptr, args->tz);
args->rv = Result;
if (!SyscallErr(Result) && args->tv) {
*args->tv = tv64;
}
}
static void clock_gettime(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
HLE::x32::compat_ptr<HLE::x32::timespec32> tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
struct timespec tp64 {};
uint64_t Result = VDSOHandlers::ClockGetTimePtr(args->clk_id, &tp64);
args->rv = Result;
if (!SyscallErr(Result) && args->tp) {
*args->tp = tp64;
}
}
static void clock_gettime64(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
HLE::x32::compat_ptr<struct timespec> tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::ClockGetTimePtr(args->clk_id, args->tp);
}
static void clock_getres(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
clockid_t clk_id;
HLE::x32::compat_ptr<HLE::x32::timespec32> tp;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
struct timespec tp64 {};
uint64_t Result = VDSOHandlers::ClockGetResPtr(args->clk_id, &tp64);
args->rv = Result;
if (!SyscallErr(Result) && args->tp) {
*args->tp = tp64;
}
}
static void getcpu(void* ArgsRV) {
struct __attribute__((packed)) ArgsRV_t {
HLE::x32::compat_ptr<uint32_t> cpu;
HLE::x32::compat_ptr<uint32_t> node;
int rv;
}* args = reinterpret_cast<ArgsRV_t*>(ArgsRV);
args->rv = VDSOHandlers::GetCPUPtr(args->cpu, args->node);
}
} // namespace VDSO
HandlerPtr Handler_time = FEX::VDSO::x32::glibc::time;
HandlerPtr Handler_gettimeofday = FEX::VDSO::x32::glibc::gettimeofday;
HandlerPtr Handler_clock_gettime = FEX::VDSO::x32::glibc::clock_gettime;
HandlerPtr Handler_clock_gettime64 = FEX::VDSO::x32::glibc::clock_gettime64;
HandlerPtr Handler_clock_getres = FEX::VDSO::x32::glibc::clock_getres;
HandlerPtr Handler_getcpu = FEX::VDSO::x32::glibc::getcpu;
} // namespace x32
class VDSOParser final {
public:
VDSOParser(const uint8_t* HeaderBase);
void* FindSymbol(std::string_view Name) const {
auto it = Symbols.find(Name);
if (it == Symbols.end()) {
return nullptr;
}
return it->second;
}
private:
fextl::map<std::string_view, void*> Symbols;
};
VDSOParser::VDSOParser(const uint8_t* HeaderBase) {
// Minimal ELF parser that only knows how to scan for dynamic symbols from VDSO.
auto Header = reinterpret_cast<const Elf64_Ehdr*>(HeaderBase);
auto SectionHeaderOffset = Header->e_shoff;
auto SectionHeaderCount = Header->e_shnum;
auto SectionHeaders = reinterpret_cast<const Elf64_Shdr*>(&HeaderBase[SectionHeaderOffset]);
// Scan for the symbol and string headers.
const Elf64_Shdr* DynamicSymbolHeader {};
const Elf64_Shdr* DynamicStringHeader {};
for (size_t i = 0; i < SectionHeaderCount; ++i) {
if (DynamicSymbolHeader && DynamicStringHeader) {
// Found both headers.
break;
}
if (SectionHeaders[i].sh_type == SHT_DYNSYM) {
// Dynamic symbol header found.
DynamicSymbolHeader = &SectionHeaders[i];
}
if (SectionHeaders[i].sh_type == SHT_STRTAB && SectionHeaders[i].sh_addr) {
// Dynamic string header found.
DynamicStringHeader = &SectionHeaders[i];
}
}
if (!DynamicSymbolHeader || !DynamicStringHeader) {
LogMan::Msg::DFmt("Couldn't parse host VDSO symbols. Falling back to glibc implementations.");
return;
}
auto NumberOfDynamicSymbols = DynamicSymbolHeader->sh_size / DynamicSymbolHeader->sh_entsize;
const char* DynamicStringTable = reinterpret_cast<const char*>(&HeaderBase[DynamicStringHeader->sh_offset]);
// Scan all the symbols and populate the look-up table.
for (size_t i = 0; i < NumberOfDynamicSymbols; ++i) {
auto Offset = DynamicSymbolHeader->sh_offset + (i * DynamicSymbolHeader->sh_entsize);
auto Symbol = reinterpret_cast<const Elf64_Sym*>(&HeaderBase[Offset]);
if (Symbol->st_info != 0) {
// Save the symbol.
const char* Name = &DynamicStringTable[Symbol->st_name];
auto SymbolPtr = HeaderBase + Symbol->st_value;
Symbols[Name] = const_cast<void*>(static_cast<const void*>(SymbolPtr));
}
}
}
void LoadHostVDSO() {
// Linux gives the VDSO ELF header base in the auxv value AT_SYSINFO_EHDR.
auto VDSOHeader = ::getauxval(AT_SYSINFO_EHDR);
if (!VDSOHeader) {
// We couldn't load VDSO, fallback to C implementations. Which will still be faster than emulated libc versions.
LogMan::Msg::IFmt("linux-vdso implementation falling back to libc. Consider enabling VDSO in your kernel.");
return;
}
auto VDSO = VDSOParser(reinterpret_cast<const uint8_t*>(VDSOHeader));
auto SymbolPtr = VDSO.FindSymbol("__kernel_time");
if (!SymbolPtr) {
SymbolPtr = VDSO.FindSymbol("__vdso_time");
}
if (SymbolPtr) {
VDSOHandlers::TimePtr = reinterpret_cast<VDSOHandlers::TimeType>(SymbolPtr);
x64::Handler_time = x64::VDSO::time;
x32::Handler_time = x32::VDSO::time;
}
SymbolPtr = VDSO.FindSymbol("__kernel_gettimeofday");
if (!SymbolPtr) {
SymbolPtr = VDSO.FindSymbol("__vdso_gettimeofday");
}
if (SymbolPtr) {
VDSOHandlers::GetTimeOfDayPtr = reinterpret_cast<VDSOHandlers::GetTimeOfDayType>(SymbolPtr);
x64::Handler_gettimeofday = x64::VDSO::gettimeofday;
x32::Handler_gettimeofday = x32::VDSO::gettimeofday;
}
SymbolPtr = VDSO.FindSymbol("__kernel_clock_gettime");
if (!SymbolPtr) {
SymbolPtr = VDSO.FindSymbol("__vdso_clock_gettime");
}
if (SymbolPtr) {
VDSOHandlers::ClockGetTimePtr = reinterpret_cast<VDSOHandlers::ClockGetTimeType>(SymbolPtr);
x64::Handler_clock_gettime = x64::VDSO::clock_gettime;
x32::Handler_clock_gettime = x32::VDSO::clock_gettime;
x32::Handler_clock_gettime64 = x32::VDSO::clock_gettime64;
}
SymbolPtr = VDSO.FindSymbol("__kernel_clock_getres");
if (!SymbolPtr) {
SymbolPtr = VDSO.FindSymbol("__vdso_clock_getres");
}
if (SymbolPtr) {
VDSOHandlers::ClockGetResPtr = reinterpret_cast<VDSOHandlers::ClockGetResType>(SymbolPtr);
x64::Handler_clock_getres = x64::VDSO::clock_getres;
x32::Handler_clock_getres = x32::VDSO::clock_getres;
}
SymbolPtr = VDSO.FindSymbol("__kernel_getcpu");
if (!SymbolPtr) {
SymbolPtr = VDSO.FindSymbol("__vdso_getcpu");
}
if (SymbolPtr) {
VDSOHandlers::GetCPUPtr = reinterpret_cast<VDSOHandlers::GetCPUType>(SymbolPtr);
x64::Handler_getcpu = x64::VDSO::getcpu;
x32::Handler_getcpu = x32::VDSO::getcpu;
}
SymbolPtr = VDSO.FindSymbol("__kernel_getrandom");
if (!SymbolPtr) {
SymbolPtr = VDSO.FindSymbol("__vdso_getrandom");
}
if (SymbolPtr) {
VDSOHandlers::GetRandomPtr = reinterpret_cast<VDSOHandlers::GetRandomType>(SymbolPtr);
x64::Handler_getrandom = x64::VDSO::getrandom;
// 32-bit doesn't have getrandom vdso
}
}
static std::array<FEXCore::IR::ThunkDefinition, 7> VDSODefinitions = {{
{
// sha256(libVDSO:time)
{0x37, 0x63, 0x46, 0xb0, 0x79, 0x06, 0x5f, 0x9d, 0x00, 0xb6, 0x8d, 0xfd, 0x9e, 0x4a, 0x62, 0xcd,
0x1e, 0x6c, 0xcc, 0x22, 0xcd, 0xb2, 0xc0, 0x17, 0x7d, 0x42, 0x6a, 0x40, 0xd1, 0xeb, 0xfa, 0xe0},
nullptr,
},
{
// sha256(libVDSO:gettimeofday)
{0x77, 0x2a, 0xde, 0x1c, 0x13, 0x2d, 0xe9, 0x48, 0xaf, 0xe0, 0xba, 0xcc, 0x6a, 0x89, 0xff, 0xca,
0x4a, 0xdc, 0xd5, 0x63, 0x2c, 0xc5, 0x62, 0x8b, 0x5d, 0xde, 0x0b, 0x15, 0x35, 0xc6, 0xc7, 0x14},
nullptr,
},
{
// sha256(libVDSO:clock_gettime)
{0x3c, 0x96, 0x9b, 0x2d, 0xc3, 0xad, 0x2b, 0x3b, 0x9c, 0x4e, 0x4d, 0xca, 0x1c, 0xe8, 0x18, 0x4a,
0x12, 0x8a, 0xe4, 0xc1, 0x56, 0x92, 0x73, 0xce, 0x65, 0x85, 0x5f, 0x65, 0x7e, 0x94, 0x26, 0xbe},
nullptr,
},
{
// sha256(libVDSO:clock_gettime64)
{0xba, 0xe9, 0x6d, 0x30, 0xc0, 0x68, 0xc6, 0xd7, 0x59, 0x04, 0xf7, 0x10, 0x06, 0x72, 0x88, 0xfd,
0x4c, 0x57, 0x0f, 0x31, 0xa5, 0xea, 0xa9, 0xb9, 0xd3, 0x8d, 0x03, 0x81, 0x50, 0x16, 0x22, 0x71},
nullptr,
},
{
// sha256(libVDSO:clock_getres)
{0xe4, 0xa1, 0xf6, 0x23, 0x35, 0xae, 0xb7, 0xb6, 0xb0, 0x37, 0xc5, 0xc3, 0xa3, 0xfd, 0xbf, 0xa2,
0xa1, 0xc8, 0x95, 0x78, 0xe5, 0x76, 0x86, 0xdb, 0x3e, 0x6c, 0x54, 0xd5, 0x02, 0x60, 0xd8, 0x6d},
nullptr,
},
{
// sha256(libVDSO:getcpu)
{0x39, 0x83, 0x39, 0x36, 0x0f, 0x68, 0xd6, 0xfc, 0xc2, 0x3a, 0x97, 0x11, 0x85, 0x09, 0xc7, 0x25,
0xbb, 0x50, 0x49, 0x55, 0x6b, 0x0c, 0x9f, 0x50, 0x37, 0xf5, 0x9d, 0xb0, 0x38, 0x58, 0x57, 0x12},
nullptr,
},
{
// sha256(libVDSO:getrandom)
{0xf8, 0x03, 0xe2, 0x70, 0xe3, 0xf1, 0xbb, 0xc1, 0x7d, 0xa7, 0x8b, 0xb3, 0x1f, 0x3e, 0xbd, 0xc6,
0x8a, 0x50, 0xd3, 0x4a, 0x1f, 0xb3, 0x4b, 0x7e, 0x32, 0xcb, 0x1e, 0x18, 0x3b, 0x7c, 0xeb, 0x4b},
nullptr,
},
}};
void LoadGuestVDSOSymbols(bool Is64Bit, char* VDSOBase) {
// We need to load symbols we care about.
if (Is64Bit) {
// We don't care about any 64-bit symbols right now.
return;
}
// 32-bit symbol loading.
const Elf32_Ehdr* Header = reinterpret_cast<const Elf32_Ehdr*>(VDSOBase);
// First walk the section headers to find the symbol table.
const Elf32_Shdr* RawShdrs = reinterpret_cast<const Elf32_Shdr*>(VDSOBase + Header->e_shoff);
const Elf32_Shdr* StrHeader = &RawShdrs[Header->e_shstrndx];
const char* SHStrings = VDSOBase + StrHeader->sh_offset;
const Elf32_Shdr* SymTableHeader {};
const Elf32_Shdr* StringTableHeader {};
for (size_t i = 0; i < Header->e_shnum; ++i) {
const auto& Header = RawShdrs[i];
if (Header.sh_type == SHT_SYMTAB && strcmp(&SHStrings[Header.sh_name], ".symtab") == 0) {
SymTableHeader = &Header;
StringTableHeader = &RawShdrs[SymTableHeader->sh_link];
break;
}
}
if (!SymTableHeader) {
// Couldn't find symbol table
return;
}
const char* StrTab = VDSOBase + StringTableHeader->sh_offset;
size_t NumSymbols = SymTableHeader->sh_size / SymTableHeader->sh_entsize;
for (size_t i = 0; i < NumSymbols; ++i) {
uint64_t offset = SymTableHeader->sh_offset + i * SymTableHeader->sh_entsize;
const Elf32_Sym* Symbol = reinterpret_cast<const Elf32_Sym*>(VDSOBase + offset);
if (ELF32_ST_VISIBILITY(Symbol->st_other) != STV_HIDDEN && Symbol->st_value != 0) {
const char* Name = &StrTab[Symbol->st_name];
if (Name[0] != '\0') {
if (strcmp(Name, "__kernel_sigreturn") == 0) {
VDSOPointers.VDSO_kernel_sigreturn = VDSOBase + Symbol->st_value;
} else if (strcmp(Name, "__kernel_rt_sigreturn") == 0) {
VDSOPointers.VDSO_kernel_rt_sigreturn = VDSOBase + Symbol->st_value;
}
}
}
}
}
void LoadUnique32BitSigreturn(VDSOMapping* Mapping, FEX::HLE::SyscallHandler* const Handler) {
// Hardcoded to one page for now
const auto PageSize = sysconf(_SC_PAGESIZE);
Mapping->OptionalMappingSize = PageSize > 0 ? PageSize : FEXCore::Utils::FEX_PAGE_SIZE;
// First 64bit page
constexpr uintptr_t LOCATION_MAX = 0x1'0000'0000;
// We need to have the sigret handler in the lower 32bits of memory space
// Scan top down and try to allocate a location
for (size_t Location = 0xFFFF'E000; Location != 0x0; Location -= PageSize) {
void* Ptr =
::mmap(reinterpret_cast<void*>(Location), PageSize, PROT_READ | PROT_WRITE, MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (Ptr != MAP_FAILED && reinterpret_cast<uintptr_t>(Ptr) >= LOCATION_MAX) {
// Failed to map in the lower 32bits
// Try again
// Can happen in the case that host kernel ignores MAP_FIXED_NOREPLACE
::munmap(Ptr, PageSize);
continue;
}
if (Ptr != MAP_FAILED) {
Mapping->OptionalSigReturnMapping = Ptr;
break;
}
}
// Can't do anything about this
// Here's hoping the application doesn't use signals
if (!Mapping->OptionalSigReturnMapping) {
return;
}
// Signal return handlers need to be bit-exact to what the Linux kernel provides in VDSO.
// GDB and unwinding libraries key off of these instructions to understand if the stack frame is a signal frame or not.
// This two code sections match exactly what libSegFault expects.
//
// Typically this handlers are provided by the 32-bit VDSO thunk library, but that isn't available in all cases.
// Falling back to this generated code segment still allows a backtrace to work, just might not show
// the symbol as VDSO since there is no ELF to parse.
constexpr std::array<uint8_t, 9> sigreturn_32_code = {
0x58, // pop eax
0xb8, 0x77, 0x00, 0x00, 0x00, // mov eax, 0x77
0xcd, 0x80, // int 0x80
0x90, // nop
};
constexpr std::array<uint8_t, 7> rt_sigreturn_32_code = {
0xb8, 0xad, 0x00, 0x00, 0x00, // mov eax, 0xad
0xcd, 0x80, // int 0x80
};
VDSOPointers.VDSO_kernel_sigreturn = Mapping->OptionalSigReturnMapping;
VDSOPointers.VDSO_kernel_rt_sigreturn =
reinterpret_cast<void*>(reinterpret_cast<uint64_t>(Mapping->OptionalSigReturnMapping) + sigreturn_32_code.size());
memcpy(reinterpret_cast<void*>(VDSOPointers.VDSO_kernel_sigreturn), &sigreturn_32_code.at(0), sigreturn_32_code.size());
memcpy(reinterpret_cast<void*>(VDSOPointers.VDSO_kernel_rt_sigreturn), &rt_sigreturn_32_code.at(0), rt_sigreturn_32_code.size());
mprotect(Mapping->OptionalSigReturnMapping, Mapping->OptionalMappingSize, PROT_READ | PROT_EXEC);
{
auto lk = FEXCore::GuardSignalDeferringSectionWithFallback(Handler->VMATracking.Mutex, nullptr);
FEX::HLE::_SyscallHandler->TrackMmap(nullptr, reinterpret_cast<uint64_t>(Mapping->OptionalSigReturnMapping),
Mapping->OptionalMappingSize, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
}
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(nullptr, reinterpret_cast<uint64_t>(Mapping->OptionalSigReturnMapping),
Mapping->OptionalMappingSize);
}
void UnloadVDSOMapping(const VDSOMapping& Mapping) {
if (Mapping.VDSOBase) {
munmap(Mapping.VDSOBase, Mapping.VDSOSize);
}
if (Mapping.OptionalSigReturnMapping) {
munmap(Mapping.OptionalSigReturnMapping, Mapping.OptionalMappingSize);
}
}
VDSOMapping LoadVDSOThunks(bool Is64Bit, FEX::HLE::SyscallHandler* const Handler) {
VDSOMapping Mapping {};
FEX_CONFIG_OPT(ThunkGuestLibs, THUNKGUESTLIBS);
FEX_CONFIG_OPT(ThunkGuestLibs32, THUNKGUESTLIBS32);
fextl::string ThunkGuestPath {};
if (Is64Bit) {
ThunkGuestPath = fextl::fmt::format("{}/libVDSO-guest.so", ThunkGuestLibs());
} else {
ThunkGuestPath = fextl::fmt::format("{}/libVDSO-guest.so", ThunkGuestLibs32());
}
// Load VDSO if we can
int VDSOFD = ::open(ThunkGuestPath.c_str(), O_RDONLY);
if (VDSOFD != -1) {
// Get file size
Mapping.VDSOSize = lseek(VDSOFD, 0, SEEK_END);
if (Mapping.VDSOSize >= 4) {
// Reset to beginning
lseek(VDSOFD, 0, SEEK_SET);
Mapping.VDSOSize = FEXCore::AlignUp(Mapping.VDSOSize, 4096);
// Map the VDSO file to memory
Mapping.VDSOBase = Handler->GuestMmap(nullptr, nullptr, Mapping.VDSOSize, PROT_READ | PROT_EXEC, MAP_SHARED, VDSOFD, 0);
// Since we found our VDSO thunk library, find our host VDSO function implementations.
LoadHostVDSO();
}
close(VDSOFD);
LoadGuestVDSOSymbols(Is64Bit, reinterpret_cast<char*>(Mapping.VDSOBase));
}
if (!Is64Bit && (!VDSOPointers.VDSO_kernel_sigreturn || !VDSOPointers.VDSO_kernel_rt_sigreturn)) {
// If VDSO couldn't find sigreturn then FEX needs to provide unique implementations.
LoadUnique32BitSigreturn(&Mapping, Handler);
}
if (Is64Bit) {
// Set the Thunk definition pointers for x86-64
VDSODefinitions[0].ThunkFunction = FEX::VDSO::x64::Handler_time;
VDSODefinitions[1].ThunkFunction = FEX::VDSO::x64::Handler_gettimeofday;
VDSODefinitions[2].ThunkFunction = FEX::VDSO::x64::Handler_clock_gettime;
VDSODefinitions[3].ThunkFunction = FEX::VDSO::x64::Handler_clock_gettime;
VDSODefinitions[4].ThunkFunction = FEX::VDSO::x64::Handler_clock_getres;
VDSODefinitions[5].ThunkFunction = FEX::VDSO::x64::Handler_getcpu;
VDSODefinitions[6].ThunkFunction = FEX::VDSO::x64::Handler_getrandom;
} else {
// Set the Thunk definition pointers for x86
VDSODefinitions[0].ThunkFunction = FEX::VDSO::x32::Handler_time;
VDSODefinitions[1].ThunkFunction = FEX::VDSO::x32::Handler_gettimeofday;
VDSODefinitions[2].ThunkFunction = FEX::VDSO::x32::Handler_clock_gettime;
VDSODefinitions[3].ThunkFunction = FEX::VDSO::x32::Handler_clock_gettime64;
VDSODefinitions[4].ThunkFunction = FEX::VDSO::x32::Handler_clock_getres;
VDSODefinitions[5].ThunkFunction = FEX::VDSO::x32::Handler_getcpu;
// getrandom doesn't exist on 32-bit, so leave VDSODefinitions[6] unfilled
}
return Mapping;
}
uint64_t GetVSyscallEntry(const void* VDSOBase) {
if (!VDSOBase) {
return 0;
}
// Extract the vsyscall location from the VDSO header.
auto Header = reinterpret_cast<const Elf32_Ehdr*>(VDSOBase);
if (Header->e_entry) {
return reinterpret_cast<uint64_t>(VDSOBase) + Header->e_entry;
}
return 0;
}
const std::span<FEXCore::IR::ThunkDefinition> GetVDSOThunkDefinitions(bool Is64Bit) {
return std::span(VDSODefinitions.begin(), VDSODefinitions.end() - (Is64Bit ? 0 : 1));
}
const VDSOSigReturn& GetVDSOSymbols() {
return VDSOPointers;
}
} // namespace FEX::VDSO