From e4bfbda00884063cc089dd69e5ed3df70e058757 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Fri, 6 Sep 2024 12:34:52 -0700 Subject: [PATCH] VDSOEmulation: Stop using dlopen for VDSO We have been relying on the `YesIKnowImNotSupposedToUseTheGlibcAllocator` fault avoidance for a long while now. The plan was to rewrite the symbol fetching in the future to avoid this since VDSO is kind of special anyway. That time is now, I have had this VDSO symbol parsing code living in a different project for a few months now and it is working great. The basic things here are that the Linux kernel provides the VDSO mapping base pointer through the auxv value `AT_SYSINFO_EHDR`. We then need a minimal ELF parser that /only/ parses the dynamic symbol header (and accompanying string header). Then it's a simple case of walking the symbol table and recording the pointers. Confirmed this fetches all the correct symbols (As I've been using it for a while already I already knew it worked.) This lets us stop allocating memory through the glibc allocator due to dlopen. --- .../Tools/LinuxEmulation/VDSO_Emulation.cpp | 94 +++++++++++++++---- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/Source/Tools/LinuxEmulation/VDSO_Emulation.cpp b/Source/Tools/LinuxEmulation/VDSO_Emulation.cpp index 47b31a114b..d04732d4ce 100644 --- a/Source/Tools/LinuxEmulation/VDSO_Emulation.cpp +++ b/Source/Tools/LinuxEmulation/VDSO_Emulation.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -362,24 +363,80 @@ namespace x32 { HandlerPtr Handler_getcpu = FEX::VDSO::x32::glibc::getcpu; } // namespace x32 -void LoadHostVDSO() { - // dlopen does allocations that FEX can't track. - // Ensure we don't run afoul of the glibc fault allocator. - FEXCore::Allocator::YesIKnowImNotSupposedToUseTheGlibcAllocator glibc; - void* vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); - if (!vdso) { - vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); +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 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(HeaderBase); + auto SectionHeaderOffset = Header->e_shoff; + auto SectionHeaderCount = Header->e_shnum; + auto SectionHeaders = reinterpret_cast(&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 (!vdso) { + auto NumberOfDynamicSymbols = DynamicSymbolHeader->sh_size / DynamicSymbolHeader->sh_entsize; + const char* DynamicStringTable = reinterpret_cast(&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(&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(static_cast(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 SymbolPtr = dlsym(vdso, "__kernel_time"); + auto VDSO = VDSOParser(reinterpret_cast(VDSOHeader)); + + auto SymbolPtr = VDSO.FindSymbol("__kernel_time"); if (!SymbolPtr) { - SymbolPtr = dlsym(vdso, "__vdso_time"); + SymbolPtr = VDSO.FindSymbol("__vdso_time"); } if (SymbolPtr) { VDSOHandlers::TimePtr = reinterpret_cast(SymbolPtr); @@ -387,9 +444,9 @@ void LoadHostVDSO() { x32::Handler_time = x32::VDSO::time; } - SymbolPtr = dlsym(vdso, "__kernel_gettimeofday"); + SymbolPtr = VDSO.FindSymbol("__kernel_gettimeofday"); if (!SymbolPtr) { - SymbolPtr = dlsym(vdso, "__vdso_gettimeofday"); + SymbolPtr = VDSO.FindSymbol("__vdso_gettimeofday"); } if (SymbolPtr) { @@ -398,9 +455,9 @@ void LoadHostVDSO() { x32::Handler_gettimeofday = x32::VDSO::gettimeofday; } - SymbolPtr = dlsym(vdso, "__kernel_clock_gettime"); + SymbolPtr = VDSO.FindSymbol("__kernel_clock_gettime"); if (!SymbolPtr) { - SymbolPtr = dlsym(vdso, "__vdso_clock_gettime"); + SymbolPtr = VDSO.FindSymbol("__vdso_clock_gettime"); } if (SymbolPtr) { @@ -410,9 +467,9 @@ void LoadHostVDSO() { x32::Handler_clock_gettime64 = x32::VDSO::clock_gettime64; } - SymbolPtr = dlsym(vdso, "__kernel_clock_getres"); + SymbolPtr = VDSO.FindSymbol("__kernel_clock_getres"); if (!SymbolPtr) { - SymbolPtr = dlsym(vdso, "__vdso_clock_getres"); + SymbolPtr = VDSO.FindSymbol("__vdso_clock_getres"); } if (SymbolPtr) { @@ -421,9 +478,9 @@ void LoadHostVDSO() { x32::Handler_clock_getres = x32::VDSO::clock_getres; } - SymbolPtr = dlsym(vdso, "__kernel_getcpu"); + SymbolPtr = VDSO.FindSymbol("__kernel_getcpu"); if (!SymbolPtr) { - SymbolPtr = dlsym(vdso, "__vdso_getcpu"); + SymbolPtr = VDSO.FindSymbol("__vdso_getcpu"); } if (SymbolPtr) { @@ -431,7 +488,6 @@ void LoadHostVDSO() { x64::Handler_getcpu = x64::VDSO::getcpu; x32::Handler_getcpu = x32::VDSO::getcpu; } - dlclose(vdso); } static std::array VDSODefinitions = {{