#include "src/__support/OSUtil/linux/vdso.h"
#include "hdr/link_macros.h"
#include "hdr/sys_auxv_macros.h"
#include "src/__support/CPP/array.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/linux/auxv.h"
#include "src/__support/threads/callonce.h"
#include "src/__support/threads/linux/futex_word.h"
#include <linux/auxvec.h>
namespace LIBC_NAMESPACE_DECL {
namespace vdso {
Symbol::VDSOArray Symbol::global_cache{};
CallOnceFlag Symbol::once_flag = callonce_impl::NOT_CALLED;
namespace {
struct Verdaux {
ElfW(Word) vda_name;
ElfW(Word) vda_next;
entry */
};
struct Verdef {
ElfW(Half) vd_version;
ElfW(Half) vd_flags;
ElfW(Half) vd_ndx;
ElfW(Half) vd_cnt;
ElfW(Word) vd_hash;
ElfW(Word) vd_aux;
ElfW(Word) vd_next;
Verdef *next() const {
if (vd_next == 0)
return nullptr;
return reinterpret_cast<Verdef *>(reinterpret_cast<uintptr_t>(this) +
vd_next);
}
Verdaux *aux() const {
return reinterpret_cast<Verdaux *>(reinterpret_cast<uintptr_t>(this) +
vd_aux);
}
};
cpp::string_view find_version(Verdef *verdef, ElfW(Half) * versym,
const char *strtab, size_t idx) {
#ifndef VER_FLG_BASE
constexpr ElfW(Half) VER_FLG_BASE = 0x1;
#endif
if (!versym)
return "";
ElfW(Half) identifier = versym[idx] & 0x7FFF;
for (Verdef *def = verdef; def != nullptr; def = def->next()) {
if (def->vd_flags & VER_FLG_BASE)
continue;
if ((def->vd_ndx & 0x7FFF) == identifier) {
Verdaux *aux = def->aux();
return strtab + aux->vda_name;
}
}
return "";
}
size_t shdr_get_symbol_count(ElfW(Shdr) * vdso_shdr, size_t e_shnum) {
if (!vdso_shdr)
return 0;
for (size_t i = 0; i < e_shnum; ++i) {
if (vdso_shdr[i].sh_type == SHT_DYNSYM)
return vdso_shdr[i].sh_size / vdso_shdr[i].sh_entsize;
}
return 0;
}
struct VDSOSymbolTable {
const char *strtab;
ElfW(Sym) * symtab;
ElfW(Half) * versym;
Verdef *verdef;
void populate_symbol_cache(Symbol::VDSOArray &symbol_table,
size_t symbol_count, ElfW(Addr) vdso_addr) {
for (size_t i = 0, e = symbol_table.size(); i < e; ++i) {
Symbol sym = i;
cpp::string_view name = sym.name();
cpp::string_view version = sym.version();
if (name.empty())
continue;
for (size_t j = 0; j < symbol_count; ++j) {
if (name == strtab + symtab[j].st_name) {
if (versym && verdef &&
version != find_version(verdef, versym, strtab, j))
continue;
symbol_table[i] =
reinterpret_cast<void *>(vdso_addr + symtab[j].st_value);
}
}
}
}
};
struct PhdrInfo {
ElfW(Addr) vdso_addr;
ElfW(Dyn) * vdso_dyn;
static cpp::optional<PhdrInfo> from(ElfW(Phdr) * vdso_phdr, size_t e_phnum,
uintptr_t vdso_ehdr_addr) {
constexpr ElfW(Addr) INVALID_ADDR = static_cast<ElfW(Addr)>(-1);
ElfW(Addr) vdso_addr = INVALID_ADDR;
ElfW(Dyn) *vdso_dyn = nullptr;
if (!vdso_phdr)
return cpp::nullopt;
for (size_t i = 0; i < e_phnum; ++i) {
if (vdso_phdr[i].p_type == PT_DYNAMIC)
vdso_dyn = reinterpret_cast<ElfW(Dyn) *>(vdso_ehdr_addr +
vdso_phdr[i].p_offset);
if (vdso_phdr[i].p_type == PT_LOAD)
vdso_addr =
vdso_ehdr_addr + vdso_phdr[i].p_offset - vdso_phdr[i].p_vaddr;
if (vdso_addr && vdso_dyn)
return PhdrInfo{vdso_addr, vdso_dyn};
}
return cpp::nullopt;
}
cpp::optional<VDSOSymbolTable> populate_symbol_table() {
const char *strtab = nullptr;
ElfW(Sym) *symtab = nullptr;
ElfW(Half) *versym = nullptr;
Verdef *verdef = nullptr;
for (ElfW(Dyn) *d = vdso_dyn; d->d_tag != DT_NULL; ++d) {
switch (d->d_tag) {
case DT_STRTAB:
strtab = reinterpret_cast<const char *>(vdso_addr + d->d_un.d_ptr);
break;
case DT_SYMTAB:
symtab = reinterpret_cast<ElfW(Sym) *>(vdso_addr + d->d_un.d_ptr);
break;
case DT_VERSYM:
versym = reinterpret_cast<uint16_t *>(vdso_addr + d->d_un.d_ptr);
break;
case DT_VERDEF:
verdef = reinterpret_cast<Verdef *>(vdso_addr + d->d_un.d_ptr);
break;
}
if (strtab && symtab && versym && verdef)
break;
}
if (strtab == nullptr || symtab == nullptr)
return cpp::nullopt;
return VDSOSymbolTable{strtab, symtab, versym, verdef};
}
};
}
void Symbol::initialize_vdso_global_cache() {
for (auto &i : global_cache)
i = nullptr;
cpp::optional<unsigned long> auxv_res = auxv::get(AT_SYSINFO_EHDR);
uintptr_t vdso_ehdr_addr = auxv_res ? static_cast<uintptr_t>(*auxv_res) : 0;
auto vdso_ehdr = reinterpret_cast<ElfW(Ehdr) *>(vdso_ehdr_addr);
if (vdso_ehdr == nullptr)
return;
auto vdso_shdr =
reinterpret_cast<ElfW(Shdr) *>(vdso_ehdr_addr + vdso_ehdr->e_shoff);
size_t symbol_count = shdr_get_symbol_count(vdso_shdr, vdso_ehdr->e_shnum);
if (symbol_count == 0)
return;
ElfW(Phdr) *vdso_phdr =
reinterpret_cast<ElfW(Phdr) *>(vdso_ehdr_addr + vdso_ehdr->e_phoff);
cpp::optional<PhdrInfo> phdr_info =
PhdrInfo::from(vdso_phdr, vdso_ehdr->e_phnum, vdso_ehdr_addr);
if (!phdr_info.has_value())
return;
cpp::optional<VDSOSymbolTable> vdso_symbol_table =
phdr_info->populate_symbol_table();
if (!vdso_symbol_table.has_value())
return;
vdso_symbol_table->populate_symbol_cache(global_cache, symbol_count,
phdr_info->vdso_addr);
}
}
}