Files
FEX-Emu--FEX/unittests/FEXLinuxTests/tests/vdso/vdso_test.cpp
T
Paulo Matos 2b4ec88dae Whole-tree reformat
This follows discussions from #3413.
Followup commits add clang-format file, script and blame ignore lists.
2024-04-12 16:26:02 +02:00

208 lines
5.5 KiB
C++

#include <map>
#include <sys/auxv.h>
#include <string>
#include <unistd.h>
#include <fstream>
#include <elf.h>
#include <dlfcn.h>
#include <cstdint>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <catch2/catch_test_macros.hpp>
using time_type = int (*)(time_t* tloc);
time_type time_vdso = (time_type)::time;
using gettimeofday_type = int (*)(struct timeval* tv, struct timezone* tz);
gettimeofday_type gettimeofday_vdso = (gettimeofday_type)::gettimeofday;
using gettime_type = int (*)(clockid_t, struct timespec*);
gettime_type gettime_vdso = (gettime_type)::clock_gettime;
using getres_type = int (*)(clockid_t, struct timespec*);
getres_type getres_vdso = (getres_type)::clock_getres;
using getcpu_type = int (*)(uint32_t* cpu, uint32_t* node);
getcpu_type getcpu_vdso = (getcpu_type)::getcpu;
#if __SIZEOF_POINTER__ == 4
struct timespec64 {
int64_t tv_sec;
int64_t tv_nsec;
};
using gettime64_type = int (*)(clockid_t, struct timespec64*);
gettime64_type gettime64_vdso = nullptr;
#endif
class VDSOParser {
#if __SIZEOF_POINTER__ == 8
using ELFHeader = Elf64_Ehdr;
using ELFSectionHeader = Elf64_Shdr;
using ELFSymbol = Elf64_Sym;
#else
using ELFHeader = Elf32_Ehdr;
using ELFSectionHeader = Elf32_Shdr;
using ELFSymbol = Elf32_Sym;
#endif
public:
VDSOParser(uint8_t* Ptr) {
ELFHeader* Header = (ELFHeader*)Ptr;
uint64_t SectionHeaderOffset = Header->e_shoff;
uint16_t SectionHeaderCount = Header->e_shnum;
ELFSectionHeader* SHdrs = (ELFSectionHeader*)(&Ptr[SectionHeaderOffset]);
const ELFSectionHeader* DynamicSymHeader {nullptr};
const ELFSectionHeader* DynamicStringHeader {nullptr};
for (size_t i = 0; i < SectionHeaderCount; ++i) {
if (SHdrs[i].sh_type == SHT_STRTAB && SHdrs[i].sh_addr) {
DynamicStringHeader = &SHdrs[i];
}
if (SHdrs[i].sh_type == SHT_DYNSYM) {
DynamicSymHeader = &SHdrs[i];
}
}
size_t NumDynSymSymbols = DynamicSymHeader->sh_size / DynamicSymHeader->sh_entsize;
const char* DynStrTab = reinterpret_cast<const char*>(&Ptr[DynamicStringHeader->sh_offset]);
for (size_t i = 0; i < NumDynSymSymbols; ++i) {
uint64_t offset = DynamicSymHeader->sh_offset + i * DynamicSymHeader->sh_entsize;
const ELFSymbol* Symbol = reinterpret_cast<const ELFSymbol*>(&Ptr[offset]);
const char* Name = &DynStrTab[Symbol->st_name];
if (Symbol->st_info != 0) {
uint8_t* SymbolPtr = Symbol->st_value + Ptr;
VDSOSymbols[Name] = SymbolPtr;
printf("Found VDSO symbol '%s' at %p\n", Name, SymbolPtr);
}
}
}
uint8_t* GetVDSOSymbol(const char* String) {
auto it = VDSOSymbols.find(String);
if (it != VDSOSymbols.end()) {
return it->second;
}
return nullptr;
}
std::map<std::string, uint8_t*> VDSOSymbols;
};
static void LoadVDSO() {
uint64_t Begin = ::getauxval(AT_SYSINFO_EHDR);
if (!Begin) {
printf("No VDSO\n");
return;
}
VDSOParser VDSO((uint8_t*)Begin);
auto it = VDSO.GetVDSOSymbol("__vdso_time");
if (it) {
time_vdso = reinterpret_cast<time_type>(it);
}
it = VDSO.GetVDSOSymbol("__vdso_gettimeofday");
if (it) {
gettimeofday_vdso = reinterpret_cast<gettimeofday_type>(it);
}
it = VDSO.GetVDSOSymbol("__vdso_clock_gettime");
if (it) {
gettime_vdso = reinterpret_cast<gettime_type>(it);
}
it = VDSO.GetVDSOSymbol("__vdso_clock_getres");
if (it) {
getres_vdso = reinterpret_cast<getres_type>(it);
}
it = VDSO.GetVDSOSymbol("__vdso_getcpu");
if (it) {
getcpu_vdso = reinterpret_cast<getcpu_type>(it);
}
#if __SIZEOF_POINTER__ == 4
it = VDSO.GetVDSOSymbol("__vdso_clock_gettime64");
if (it) {
gettime64_vdso = reinterpret_cast<gettime64_type>(it);
}
#endif
}
TEST_CASE("VDSO") {
LoadVDSO();
REQUIRE(time_vdso != 0);
REQUIRE(gettimeofday_vdso != 0);
REQUIRE(gettime_vdso != 0);
REQUIRE(getres_vdso != 0);
REQUIRE(getcpu_vdso != 0);
// There are few strict guarantees on the return values of these functions,
// so instead we make some educated guesses to check for valid outputs below
time_t tloc {};
{
int result = time_vdso(&tloc);
printf("time\n");
CHECK(result != -1);
printf("\tResult: %d\n", result);
printf("\tTime_t: 0x%lx\n", tloc);
CHECK(tloc > 946684800); // Ensure it's later than year 2000
}
{
timeval tv {};
int result = gettimeofday_vdso(&tv, nullptr);
printf("gettimeofday\n");
CHECK(result == 0);
printf("\tTime: 0x%lx 0x%lx\n", tv.tv_sec, tv.tv_usec);
// Ensure gettimeofday and time results are consistent
CHECK(tv.tv_sec >= tloc);
CHECK(tv.tv_sec <= tloc + 1);
}
{
timespec ts {};
int result = gettime_vdso(CLOCK_MONOTONIC, &ts);
printf("clock_gettime\n");
CHECK(result == 0);
printf("\tTime: 0x%lx 0x%lx\n", ts.tv_sec, ts.tv_nsec);
}
{
timespec ts {};
int result = getres_vdso(CLOCK_MONOTONIC, &ts);
printf("clock_getres\n");
CHECK(result == 0);
printf("\tTime: 0x%lx 0x%lx\n", ts.tv_sec, ts.tv_nsec);
CHECK(ts.tv_sec == 0);
CHECK(ts.tv_nsec > 0);
}
{
uint32_t cpu, node;
int result = getcpu_vdso(&cpu, &node);
printf("getcpu\n");
CHECK(result == 0);
printf("\tCPU: 0x%x\n", cpu);
printf("\tNode: 0x%x\n", node);
}
#if __SIZEOF_POINTER__ == 4
if (gettime64_vdso) {
timespec64 ts {};
int result = gettime64_vdso(CLOCK_MONOTONIC, &ts);
printf("clock_gettime64\n");
CHECK(result == 0);
printf("\tTime: 0x%llx 0x%llx\n", ts.tv_sec, ts.tv_nsec);
}
#endif
}