1 /* 2 * parse_vdso.c: Linux reference vDSO parser 3 * Written by Andrew Lutomirski, 2011. 4 * 5 * This code is meant to be linked in to various programs that run on Linux. 6 * As such, it is available with as few restrictions as possible. This file 7 * is licensed under the Creative Commons Zero License, version 1.0, 8 * available at http://creativecommons.org/publicdomain/zero/1.0/legalcode 9 * 10 * The vDSO is a regular ELF DSO that the kernel maps into user space when 11 * it starts a program. It works equally well in statically and dynamically 12 * linked binaries. 13 * 14 * This code is tested on x86_64. In principle it should work on any 64-bit 15 * architecture that has a vDSO. 16 */ 17 18 #include <stdbool.h> 19 #include <stdint.h> 20 #include <string.h> 21 #include <elf.h> 22 23 /* 24 * To use this vDSO parser, first call one of the vdso_init_* functions. 25 * If you've already parsed auxv, then pass the value of AT_SYSINFO_EHDR 26 * to vdso_init_from_sysinfo_ehdr. Otherwise pass auxv to vdso_init_from_auxv. 27 * Then call vdso_sym for each symbol you want. For example, to look up 28 * gettimeofday on x86_64, use: 29 * 30 * <some pointer> = vdso_sym("LINUX_2.6", "gettimeofday"); 31 * or 32 * <some pointer> = vdso_sym("LINUX_2.6", "__vdso_gettimeofday"); 33 * 34 * vdso_sym will return 0 if the symbol doesn't exist or if the init function 35 * failed or was not called. vdso_sym is a little slow, so its return value 36 * should be cached. 37 * 38 * vdso_sym is threadsafe; the init functions are not. 39 * 40 * These are the prototypes: 41 */ 42 extern void vdso_init_from_auxv(void *auxv); 43 extern void vdso_init_from_sysinfo_ehdr(uintptr_t base); 44 extern void *vdso_sym(const char *version, const char *name); 45 46 47 /* And here's the code. */ 48 49 #ifndef __x86_64__ 50 # error Not yet ported to non-x86_64 architectures 51 #endif 52 53 static struct vdso_info 54 { 55 bool valid; 56 57 /* Load information */ 58 uintptr_t load_addr; 59 uintptr_t load_offset; /* load_addr - recorded vaddr */ 60 61 /* Symbol table */ 62 Elf64_Sym *symtab; 63 const char *symstrings; 64 Elf64_Word *bucket, *chain; 65 Elf64_Word nbucket, nchain; 66 67 /* Version table */ 68 Elf64_Versym *versym; 69 Elf64_Verdef *verdef; 70 } vdso_info; 71 72 /* Straight from the ELF specification. */ 73 static unsigned long elf_hash(const unsigned char *name) 74 { 75 unsigned long h = 0, g; 76 while (*name) 77 { 78 h = (h << 4) + *name++; 79 if (g = h & 0xf0000000) 80 h ^= g >> 24; 81 h &= ~g; 82 } 83 return h; 84 } 85 86 void vdso_init_from_sysinfo_ehdr(uintptr_t base) 87 { 88 size_t i; 89 bool found_vaddr = false; 90 91 vdso_info.valid = false; 92 93 vdso_info.load_addr = base; 94 95 Elf64_Ehdr *hdr = (Elf64_Ehdr*)base; 96 Elf64_Phdr *pt = (Elf64_Phdr*)(vdso_info.load_addr + hdr->e_phoff); 97 Elf64_Dyn *dyn = 0; 98 99 /* 100 * We need two things from the segment table: the load offset 101 * and the dynamic table. 102 */ 103 for (i = 0; i < hdr->e_phnum; i++) 104 { 105 if (pt[i].p_type == PT_LOAD && !found_vaddr) { 106 found_vaddr = true; 107 vdso_info.load_offset = base 108 + (uintptr_t)pt[i].p_offset 109 - (uintptr_t)pt[i].p_vaddr; 110 } else if (pt[i].p_type == PT_DYNAMIC) { 111 dyn = (Elf64_Dyn*)(base + pt[i].p_offset); 112 } 113 } 114 115 if (!found_vaddr || !dyn) 116 return; /* Failed */ 117 118 /* 119 * Fish out the useful bits of the dynamic table. 120 */ 121 Elf64_Word *hash = 0; 122 vdso_info.symstrings = 0; 123 vdso_info.symtab = 0; 124 vdso_info.versym = 0; 125 vdso_info.verdef = 0; 126 for (i = 0; dyn[i].d_tag != DT_NULL; i++) { 127 switch (dyn[i].d_tag) { 128 case DT_STRTAB: 129 vdso_info.symstrings = (const char *) 130 ((uintptr_t)dyn[i].d_un.d_ptr 131 + vdso_info.load_offset); 132 break; 133 case DT_SYMTAB: 134 vdso_info.symtab = (Elf64_Sym *) 135 ((uintptr_t)dyn[i].d_un.d_ptr 136 + vdso_info.load_offset); 137 break; 138 case DT_HASH: 139 hash = (Elf64_Word *) 140 ((uintptr_t)dyn[i].d_un.d_ptr 141 + vdso_info.load_offset); 142 break; 143 case DT_VERSYM: 144 vdso_info.versym = (Elf64_Versym *) 145 ((uintptr_t)dyn[i].d_un.d_ptr 146 + vdso_info.load_offset); 147 break; 148 case DT_VERDEF: 149 vdso_info.verdef = (Elf64_Verdef *) 150 ((uintptr_t)dyn[i].d_un.d_ptr 151 + vdso_info.load_offset); 152 break; 153 } 154 } 155 if (!vdso_info.symstrings || !vdso_info.symtab || !hash) 156 return; /* Failed */ 157 158 if (!vdso_info.verdef) 159 vdso_info.versym = 0; 160 161 /* Parse the hash table header. */ 162 vdso_info.nbucket = hash[0]; 163 vdso_info.nchain = hash[1]; 164 vdso_info.bucket = &hash[2]; 165 vdso_info.chain = &hash[vdso_info.nbucket + 2]; 166 167 /* That's all we need. */ 168 vdso_info.valid = true; 169 } 170 171 static bool vdso_match_version(Elf64_Versym ver, 172 const char *name, Elf64_Word hash) 173 { 174 /* 175 * This is a helper function to check if the version indexed by 176 * ver matches name (which hashes to hash). 177 * 178 * The version definition table is a mess, and I don't know how 179 * to do this in better than linear time without allocating memory 180 * to build an index. I also don't know why the table has 181 * variable size entries in the first place. 182 * 183 * For added fun, I can't find a comprehensible specification of how 184 * to parse all the weird flags in the table. 185 * 186 * So I just parse the whole table every time. 187 */ 188 189 /* First step: find the version definition */ 190 ver &= 0x7fff; /* Apparently bit 15 means "hidden" */ 191 Elf64_Verdef *def = vdso_info.verdef; 192 while(true) { 193 if ((def->vd_flags & VER_FLG_BASE) == 0 194 && (def->vd_ndx & 0x7fff) == ver) 195 break; 196 197 if (def->vd_next == 0) 198 return false; /* No definition. */ 199 200 def = (Elf64_Verdef *)((char *)def + def->vd_next); 201 } 202 203 /* Now figure out whether it matches. */ 204 Elf64_Verdaux *aux = (Elf64_Verdaux*)((char *)def + def->vd_aux); 205 return def->vd_hash == hash 206 && !strcmp(name, vdso_info.symstrings + aux->vda_name); 207 } 208 209 void *vdso_sym(const char *version, const char *name) 210 { 211 unsigned long ver_hash; 212 if (!vdso_info.valid) 213 return 0; 214 215 ver_hash = elf_hash(version); 216 Elf64_Word chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket]; 217 218 for (; chain != STN_UNDEF; chain = vdso_info.chain[chain]) { 219 Elf64_Sym *sym = &vdso_info.symtab[chain]; 220 221 /* Check for a defined global or weak function w/ right name. */ 222 if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) 223 continue; 224 if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL && 225 ELF64_ST_BIND(sym->st_info) != STB_WEAK) 226 continue; 227 if (sym->st_shndx == SHN_UNDEF) 228 continue; 229 if (strcmp(name, vdso_info.symstrings + sym->st_name)) 230 continue; 231 232 /* Check symbol version. */ 233 if (vdso_info.versym 234 && !vdso_match_version(vdso_info.versym[chain], 235 version, ver_hash)) 236 continue; 237 238 return (void *)(vdso_info.load_offset + sym->st_value); 239 } 240 241 return 0; 242 } 243 244 void vdso_init_from_auxv(void *auxv) 245 { 246 Elf64_auxv_t *elf_auxv = auxv; 247 for (int i = 0; elf_auxv[i].a_type != AT_NULL; i++) 248 { 249 if (elf_auxv[i].a_type == AT_SYSINFO_EHDR) { 250 vdso_init_from_sysinfo_ehdr(elf_auxv[i].a_un.a_val); 251 return; 252 } 253 } 254 255 vdso_info.valid = false; 256 } 257
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.