| 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-exception |
| 6 | // |
| 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/libc_errno.h" |
| 15 | #include "src/__support/threads/callonce.h" |
| 16 | #include "src/__support/threads/linux/futex_word.h" |
| 17 | #include "src/sys/auxv/getauxval.h" |
| 18 | #include <linux/auxvec.h> |
| 19 | |
| 20 | // TODO: This is a temporary workaround to avoid including elf.h |
| 21 | // Include our own headers for ElfW and friends once we have them. |
| 22 | namespace LIBC_NAMESPACE_DECL { |
| 23 | |
| 24 | namespace vdso { |
| 25 | |
| 26 | Symbol::VDSOArray Symbol::global_cache{}; |
| 27 | CallOnceFlag Symbol::once_flag = callonce_impl::NOT_CALLED; |
| 28 | |
| 29 | namespace { |
| 30 | // See https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/symverdefs.html |
| 31 | struct Verdaux { |
| 32 | ElfW(Word) vda_name; /* Version or dependency names */ |
| 33 | ElfW(Word) vda_next; /* Offset in bytes to next verdaux |
| 34 | entry */ |
| 35 | }; |
| 36 | struct Verdef { |
| 37 | ElfW(Half) vd_version; /* Version revision */ |
| 38 | ElfW(Half) vd_flags; /* Version information */ |
| 39 | ElfW(Half) vd_ndx; /* Version Index */ |
| 40 | ElfW(Half) vd_cnt; /* Number of associated aux entries */ |
| 41 | ElfW(Word) vd_hash; /* Version name hash value */ |
| 42 | ElfW(Word) vd_aux; /* Offset in bytes to verdaux array */ |
| 43 | ElfW(Word) vd_next; /* Offset in bytes to next verdef entry */ |
| 44 | Verdef *next() const { |
| 45 | if (vd_next == 0) |
| 46 | return nullptr; |
| 47 | return reinterpret_cast<Verdef *>(reinterpret_cast<uintptr_t>(this) + |
| 48 | vd_next); |
| 49 | } |
| 50 | Verdaux *aux() const { |
| 51 | return reinterpret_cast<Verdaux *>(reinterpret_cast<uintptr_t>(this) + |
| 52 | vd_aux); |
| 53 | } |
| 54 | }; |
| 55 | |
| 56 | // version search procedure specified by |
| 57 | // https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/symversion.html#SYMVERTBL |
| 58 | cpp::string_view find_version(Verdef *verdef, ElfW(Half) * versym, |
| 59 | const char *strtab, size_t idx) { |
| 60 | #ifndef VER_FLG_BASE |
| 61 | constexpr ElfW(Half) VER_FLG_BASE = 0x1; |
| 62 | #endif |
| 63 | if (!versym) |
| 64 | return "" ; |
| 65 | ElfW(Half) identifier = versym[idx] & 0x7FFF; |
| 66 | // iterate through all version definitions |
| 67 | for (Verdef *def = verdef; def != nullptr; def = def->next()) { |
| 68 | // skip if this is a file-level version |
| 69 | if (def->vd_flags & VER_FLG_BASE) |
| 70 | continue; |
| 71 | // check if the version identifier matches. Highest bit is used to determine |
| 72 | // whether the symbol is local. Only lower 15 bits are used for version |
| 73 | // identifier. |
| 74 | if ((def->vd_ndx & 0x7FFF) == identifier) { |
| 75 | Verdaux *aux = def->aux(); |
| 76 | return strtab + aux->vda_name; |
| 77 | } |
| 78 | } |
| 79 | return "" ; |
| 80 | } |
| 81 | |
| 82 | size_t shdr_get_symbol_count(ElfW(Shdr) * vdso_shdr, size_t e_shnum) { |
| 83 | if (!vdso_shdr) |
| 84 | return 0; |
| 85 | // iterate all sections until we locate the dynamic symbol section |
| 86 | for (size_t i = 0; i < e_shnum; ++i) { |
| 87 | // dynamic symbol section is a table section |
| 88 | // therefore, the number of entries can be computed as the ratio |
| 89 | // of the section size to the size of a single entry |
| 90 | if (vdso_shdr[i].sh_type == SHT_DYNSYM) |
| 91 | return vdso_shdr[i].sh_size / vdso_shdr[i].sh_entsize; |
| 92 | } |
| 93 | return 0; |
| 94 | } |
| 95 | |
| 96 | struct VDSOSymbolTable { |
| 97 | const char *strtab; |
| 98 | ElfW(Sym) * symtab; |
| 99 | // The following can be nullptr if the vDSO does not have versioning |
| 100 | ElfW(Half) * versym; |
| 101 | Verdef *verdef; |
| 102 | |
| 103 | void populate_symbol_cache(Symbol::VDSOArray &symbol_table, |
| 104 | size_t symbol_count, ElfW(Addr) vdso_addr) { |
| 105 | for (size_t i = 0, e = symbol_table.size(); i < e; ++i) { |
| 106 | Symbol sym = i; |
| 107 | cpp::string_view name = sym.name(); |
| 108 | cpp::string_view version = sym.version(); |
| 109 | if (name.empty()) |
| 110 | continue; |
| 111 | |
| 112 | for (size_t j = 0; j < symbol_count; ++j) { |
| 113 | if (name == strtab + symtab[j].st_name) { |
| 114 | // we find a symbol with desired name |
| 115 | // now we need to check if it has the right version |
| 116 | if (versym && verdef && |
| 117 | version != find_version(verdef, versym, strtab, j)) |
| 118 | continue; |
| 119 | |
| 120 | // put the symbol address into the symbol table |
| 121 | symbol_table[i] = |
| 122 | reinterpret_cast<void *>(vdso_addr + symtab[j].st_value); |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | }; |
| 128 | |
| 129 | struct PhdrInfo { |
| 130 | ElfW(Addr) vdso_addr; |
| 131 | ElfW(Dyn) * vdso_dyn; |
| 132 | static cpp::optional<PhdrInfo> from(ElfW(Phdr) * vdso_phdr, size_t e_phnum, |
| 133 | uintptr_t vdso_ehdr_addr) { |
| 134 | constexpr ElfW(Addr) INVALID_ADDR = static_cast<ElfW(Addr)>(-1); |
| 135 | ElfW(Addr) vdso_addr = INVALID_ADDR; |
| 136 | ElfW(Dyn) *vdso_dyn = nullptr; |
| 137 | if (!vdso_phdr) |
| 138 | return cpp::nullopt; |
| 139 | // iterate through all the program headers until we get the desired pieces |
| 140 | for (size_t i = 0; i < e_phnum; ++i) { |
| 141 | if (vdso_phdr[i].p_type == PT_DYNAMIC) |
| 142 | vdso_dyn = reinterpret_cast<ElfW(Dyn) *>(vdso_ehdr_addr + |
| 143 | vdso_phdr[i].p_offset); |
| 144 | |
| 145 | if (vdso_phdr[i].p_type == PT_LOAD) |
| 146 | vdso_addr = |
| 147 | vdso_ehdr_addr + vdso_phdr[i].p_offset - vdso_phdr[i].p_vaddr; |
| 148 | |
| 149 | if (vdso_addr && vdso_dyn) |
| 150 | return PhdrInfo{vdso_addr, vdso_dyn}; |
| 151 | } |
| 152 | |
| 153 | return cpp::nullopt; |
| 154 | } |
| 155 | |
| 156 | cpp::optional<VDSOSymbolTable> populate_symbol_table() { |
| 157 | const char *strtab = nullptr; |
| 158 | ElfW(Sym) *symtab = nullptr; |
| 159 | ElfW(Half) *versym = nullptr; |
| 160 | Verdef *verdef = nullptr; |
| 161 | for (ElfW(Dyn) *d = vdso_dyn; d->d_tag != DT_NULL; ++d) { |
| 162 | switch (d->d_tag) { |
| 163 | case DT_STRTAB: |
| 164 | strtab = reinterpret_cast<const char *>(vdso_addr + d->d_un.d_ptr); |
| 165 | break; |
| 166 | case DT_SYMTAB: |
| 167 | symtab = reinterpret_cast<ElfW(Sym) *>(vdso_addr + d->d_un.d_ptr); |
| 168 | break; |
| 169 | case DT_VERSYM: |
| 170 | versym = reinterpret_cast<uint16_t *>(vdso_addr + d->d_un.d_ptr); |
| 171 | break; |
| 172 | case DT_VERDEF: |
| 173 | verdef = reinterpret_cast<Verdef *>(vdso_addr + d->d_un.d_ptr); |
| 174 | break; |
| 175 | } |
| 176 | if (strtab && symtab && versym && verdef) |
| 177 | break; |
| 178 | } |
| 179 | if (strtab == nullptr || symtab == nullptr) |
| 180 | return cpp::nullopt; |
| 181 | |
| 182 | return VDSOSymbolTable{strtab, symtab, versym, verdef}; |
| 183 | } |
| 184 | }; |
| 185 | } // namespace |
| 186 | |
| 187 | void Symbol::initialize_vdso_global_cache() { |
| 188 | // first clear the symbol table |
| 189 | for (auto &i : global_cache) |
| 190 | i = nullptr; |
| 191 | |
| 192 | // get the address of the VDSO, protect errno since getauxval may change |
| 193 | // it |
| 194 | int errno_backup = libc_errno; |
| 195 | uintptr_t vdso_ehdr_addr = getauxval(AT_SYSINFO_EHDR); |
| 196 | // Get the memory address of the vDSO ELF header. |
| 197 | auto vdso_ehdr = reinterpret_cast<ElfW(Ehdr) *>(vdso_ehdr_addr); |
| 198 | // leave the table unpopulated if we don't have vDSO |
| 199 | if (vdso_ehdr == nullptr) { |
| 200 | libc_errno = errno_backup; |
| 201 | return; |
| 202 | } |
| 203 | |
| 204 | // locate the section header inside the elf using the section header |
| 205 | // offset |
| 206 | auto vdso_shdr = |
| 207 | reinterpret_cast<ElfW(Shdr) *>(vdso_ehdr_addr + vdso_ehdr->e_shoff); |
| 208 | size_t symbol_count = shdr_get_symbol_count(vdso_shdr, vdso_ehdr->e_shnum); |
| 209 | |
| 210 | // early return if no symbol is found |
| 211 | if (symbol_count == 0) |
| 212 | return; |
| 213 | |
| 214 | // We need to find both the loadable segment and the dynamic linking of |
| 215 | // the vDSO. compute vdso_phdr as the program header using the program |
| 216 | // header offset |
| 217 | ElfW(Phdr) *vdso_phdr = |
| 218 | reinterpret_cast<ElfW(Phdr) *>(vdso_ehdr_addr + vdso_ehdr->e_phoff); |
| 219 | cpp::optional<PhdrInfo> phdr_info = |
| 220 | PhdrInfo::from(vdso_phdr, vdso_ehdr->e_phnum, vdso_ehdr_addr); |
| 221 | // early return if either the dynamic linking or the loadable segment is |
| 222 | // not found |
| 223 | if (!phdr_info.has_value()) |
| 224 | return; |
| 225 | |
| 226 | // now, locate several more tables inside the dynmaic linking section |
| 227 | cpp::optional<VDSOSymbolTable> vdso_symbol_table = |
| 228 | phdr_info->populate_symbol_table(); |
| 229 | |
| 230 | // early return if we can't find any required fields of the symbol table |
| 231 | if (!vdso_symbol_table.has_value()) |
| 232 | return; |
| 233 | |
| 234 | // finally, populate the global symbol table cache |
| 235 | vdso_symbol_table->populate_symbol_cache(global_cache, symbol_count, |
| 236 | phdr_info->vdso_addr); |
| 237 | } |
| 238 | } // namespace vdso |
| 239 | } // namespace LIBC_NAMESPACE_DECL |
| 240 | |