235 lines · cpp
1//===------------- Linux VDSO Implementation --------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "src/__support/OSUtil/linux/vdso.h"9#include "hdr/link_macros.h"10#include "hdr/sys_auxv_macros.h"11#include "src/__support/CPP/array.h"12#include "src/__support/CPP/optional.h"13#include "src/__support/CPP/string_view.h"14#include "src/__support/OSUtil/linux/auxv.h"15#include "src/__support/threads/callonce.h"16#include "src/__support/threads/linux/futex_word.h"17#include <linux/auxvec.h>18 19// TODO: This is a temporary workaround to avoid including elf.h20// Include our own headers for ElfW and friends once we have them.21namespace LIBC_NAMESPACE_DECL {22 23namespace vdso {24 25Symbol::VDSOArray Symbol::global_cache{};26CallOnceFlag Symbol::once_flag = callonce_impl::NOT_CALLED;27 28namespace {29// See https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/symverdefs.html30struct Verdaux {31 ElfW(Word) vda_name; /* Version or dependency names */32 ElfW(Word) vda_next; /* Offset in bytes to next verdaux33 entry */34};35struct Verdef {36 ElfW(Half) vd_version; /* Version revision */37 ElfW(Half) vd_flags; /* Version information */38 ElfW(Half) vd_ndx; /* Version Index */39 ElfW(Half) vd_cnt; /* Number of associated aux entries */40 ElfW(Word) vd_hash; /* Version name hash value */41 ElfW(Word) vd_aux; /* Offset in bytes to verdaux array */42 ElfW(Word) vd_next; /* Offset in bytes to next verdef entry */43 Verdef *next() const {44 if (vd_next == 0)45 return nullptr;46 return reinterpret_cast<Verdef *>(reinterpret_cast<uintptr_t>(this) +47 vd_next);48 }49 Verdaux *aux() const {50 return reinterpret_cast<Verdaux *>(reinterpret_cast<uintptr_t>(this) +51 vd_aux);52 }53};54 55// version search procedure specified by56// https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/symversion.html#SYMVERTBL57cpp::string_view find_version(Verdef *verdef, ElfW(Half) * versym,58 const char *strtab, size_t idx) {59#ifndef VER_FLG_BASE60 constexpr ElfW(Half) VER_FLG_BASE = 0x1;61#endif62 if (!versym)63 return "";64 ElfW(Half) identifier = versym[idx] & 0x7FFF;65 // iterate through all version definitions66 for (Verdef *def = verdef; def != nullptr; def = def->next()) {67 // skip if this is a file-level version68 if (def->vd_flags & VER_FLG_BASE)69 continue;70 // check if the version identifier matches. Highest bit is used to determine71 // whether the symbol is local. Only lower 15 bits are used for version72 // identifier.73 if ((def->vd_ndx & 0x7FFF) == identifier) {74 Verdaux *aux = def->aux();75 return strtab + aux->vda_name;76 }77 }78 return "";79}80 81size_t shdr_get_symbol_count(ElfW(Shdr) * vdso_shdr, size_t e_shnum) {82 if (!vdso_shdr)83 return 0;84 // iterate all sections until we locate the dynamic symbol section85 for (size_t i = 0; i < e_shnum; ++i) {86 // dynamic symbol section is a table section87 // therefore, the number of entries can be computed as the ratio88 // of the section size to the size of a single entry89 if (vdso_shdr[i].sh_type == SHT_DYNSYM)90 return vdso_shdr[i].sh_size / vdso_shdr[i].sh_entsize;91 }92 return 0;93}94 95struct VDSOSymbolTable {96 const char *strtab;97 ElfW(Sym) * symtab;98 // The following can be nullptr if the vDSO does not have versioning99 ElfW(Half) * versym;100 Verdef *verdef;101 102 void populate_symbol_cache(Symbol::VDSOArray &symbol_table,103 size_t symbol_count, ElfW(Addr) vdso_addr) {104 for (size_t i = 0, e = symbol_table.size(); i < e; ++i) {105 Symbol sym = i;106 cpp::string_view name = sym.name();107 cpp::string_view version = sym.version();108 if (name.empty())109 continue;110 111 for (size_t j = 0; j < symbol_count; ++j) {112 if (name == strtab + symtab[j].st_name) {113 // we find a symbol with desired name114 // now we need to check if it has the right version115 if (versym && verdef &&116 version != find_version(verdef, versym, strtab, j))117 continue;118 119 // put the symbol address into the symbol table120 symbol_table[i] =121 reinterpret_cast<void *>(vdso_addr + symtab[j].st_value);122 }123 }124 }125 }126};127 128struct PhdrInfo {129 ElfW(Addr) vdso_addr;130 ElfW(Dyn) * vdso_dyn;131 static cpp::optional<PhdrInfo> from(ElfW(Phdr) * vdso_phdr, size_t e_phnum,132 uintptr_t vdso_ehdr_addr) {133 constexpr ElfW(Addr) INVALID_ADDR = static_cast<ElfW(Addr)>(-1);134 ElfW(Addr) vdso_addr = INVALID_ADDR;135 ElfW(Dyn) *vdso_dyn = nullptr;136 if (!vdso_phdr)137 return cpp::nullopt;138 // iterate through all the program headers until we get the desired pieces139 for (size_t i = 0; i < e_phnum; ++i) {140 if (vdso_phdr[i].p_type == PT_DYNAMIC)141 vdso_dyn = reinterpret_cast<ElfW(Dyn) *>(vdso_ehdr_addr +142 vdso_phdr[i].p_offset);143 144 if (vdso_phdr[i].p_type == PT_LOAD)145 vdso_addr =146 vdso_ehdr_addr + vdso_phdr[i].p_offset - vdso_phdr[i].p_vaddr;147 148 if (vdso_addr && vdso_dyn)149 return PhdrInfo{vdso_addr, vdso_dyn};150 }151 152 return cpp::nullopt;153 }154 155 cpp::optional<VDSOSymbolTable> populate_symbol_table() {156 const char *strtab = nullptr;157 ElfW(Sym) *symtab = nullptr;158 ElfW(Half) *versym = nullptr;159 Verdef *verdef = nullptr;160 for (ElfW(Dyn) *d = vdso_dyn; d->d_tag != DT_NULL; ++d) {161 switch (d->d_tag) {162 case DT_STRTAB:163 strtab = reinterpret_cast<const char *>(vdso_addr + d->d_un.d_ptr);164 break;165 case DT_SYMTAB:166 symtab = reinterpret_cast<ElfW(Sym) *>(vdso_addr + d->d_un.d_ptr);167 break;168 case DT_VERSYM:169 versym = reinterpret_cast<uint16_t *>(vdso_addr + d->d_un.d_ptr);170 break;171 case DT_VERDEF:172 verdef = reinterpret_cast<Verdef *>(vdso_addr + d->d_un.d_ptr);173 break;174 }175 if (strtab && symtab && versym && verdef)176 break;177 }178 if (strtab == nullptr || symtab == nullptr)179 return cpp::nullopt;180 181 return VDSOSymbolTable{strtab, symtab, versym, verdef};182 }183};184} // namespace185 186void Symbol::initialize_vdso_global_cache() {187 // first clear the symbol table188 for (auto &i : global_cache)189 i = nullptr;190 191 cpp::optional<unsigned long> auxv_res = auxv::get(AT_SYSINFO_EHDR);192 uintptr_t vdso_ehdr_addr = auxv_res ? static_cast<uintptr_t>(*auxv_res) : 0;193 // Get the memory address of the vDSO ELF header.194 auto vdso_ehdr = reinterpret_cast<ElfW(Ehdr) *>(vdso_ehdr_addr);195 // leave the table unpopulated if we don't have vDSO196 if (vdso_ehdr == nullptr)197 return;198 199 // locate the section header inside the elf using the section header200 // offset201 auto vdso_shdr =202 reinterpret_cast<ElfW(Shdr) *>(vdso_ehdr_addr + vdso_ehdr->e_shoff);203 size_t symbol_count = shdr_get_symbol_count(vdso_shdr, vdso_ehdr->e_shnum);204 205 // early return if no symbol is found206 if (symbol_count == 0)207 return;208 209 // We need to find both the loadable segment and the dynamic linking of210 // the vDSO. compute vdso_phdr as the program header using the program211 // header offset212 ElfW(Phdr) *vdso_phdr =213 reinterpret_cast<ElfW(Phdr) *>(vdso_ehdr_addr + vdso_ehdr->e_phoff);214 cpp::optional<PhdrInfo> phdr_info =215 PhdrInfo::from(vdso_phdr, vdso_ehdr->e_phnum, vdso_ehdr_addr);216 // early return if either the dynamic linking or the loadable segment is217 // not found218 if (!phdr_info.has_value())219 return;220 221 // now, locate several more tables inside the dynmaic linking section222 cpp::optional<VDSOSymbolTable> vdso_symbol_table =223 phdr_info->populate_symbol_table();224 225 // early return if we can't find any required fields of the symbol table226 if (!vdso_symbol_table.has_value())227 return;228 229 // finally, populate the global symbol table cache230 vdso_symbol_table->populate_symbol_cache(global_cache, symbol_count,231 phdr_info->vdso_addr);232}233} // namespace vdso234} // namespace LIBC_NAMESPACE_DECL235