on some kernel builds, the vdso exports symbols with DT_GNU_HASH but omits DT_HASH, causing the vdso not to get used and for clock_gettime to fall back to using a syscall. our vdso resolver does not use the hash table anyway, but does need the symbol count from the standard sysv hash table. if it's missing, use the GNU hashtable to calculate the number of symbols.
118 lines
2.9 KiB
C
118 lines
2.9 KiB
C
#include <elf.h>
|
|
#include <link.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include "libc.h"
|
|
#include "syscall.h"
|
|
|
|
#ifdef VDSO_USEFUL
|
|
|
|
#if ULONG_MAX == 0xffffffff
|
|
typedef Elf32_Ehdr Ehdr;
|
|
typedef Elf32_Phdr Phdr;
|
|
typedef Elf32_Sym Sym;
|
|
typedef Elf32_Verdef Verdef;
|
|
typedef Elf32_Verdaux Verdaux;
|
|
#else
|
|
typedef Elf64_Ehdr Ehdr;
|
|
typedef Elf64_Phdr Phdr;
|
|
typedef Elf64_Sym Sym;
|
|
typedef Elf64_Verdef Verdef;
|
|
typedef Elf64_Verdaux Verdaux;
|
|
#endif
|
|
|
|
static int checkver(Verdef *def, int vsym, const char *vername, char *strings)
|
|
{
|
|
vsym &= 0x7fff;
|
|
for (;;) {
|
|
if (!(def->vd_flags & VER_FLG_BASE)
|
|
&& (def->vd_ndx & 0x7fff) == vsym)
|
|
break;
|
|
if (def->vd_next == 0)
|
|
return 0;
|
|
def = (Verdef *)((char *)def + def->vd_next);
|
|
}
|
|
Verdaux *aux = (Verdaux *)((char *)def + def->vd_aux);
|
|
return !strcmp(vername, strings + aux->vda_name);
|
|
}
|
|
|
|
#define OK_TYPES (1<<STT_NOTYPE | 1<<STT_OBJECT | 1<<STT_FUNC | 1<<STT_COMMON)
|
|
#define OK_BINDS (1<<STB_GLOBAL | 1<<STB_WEAK | 1<<STB_GNU_UNIQUE)
|
|
|
|
static size_t count_syms_gnu(uint32_t *gh)
|
|
{
|
|
size_t nsym, i;
|
|
uint32_t *buckets = gh + 4 + (gh[2]*sizeof(size_t)/4);
|
|
uint32_t *hashval;
|
|
for (i = nsym = 0; i < gh[0]; i++) {
|
|
if (buckets[i] > nsym)
|
|
nsym = buckets[i];
|
|
}
|
|
if (nsym) {
|
|
hashval = buckets + gh[0] + (nsym - gh[1]);
|
|
do nsym++;
|
|
while (!(*hashval++ & 1));
|
|
}
|
|
return nsym;
|
|
}
|
|
|
|
|
|
void *__vdsosym(const char *vername, const char *name)
|
|
{
|
|
size_t i;
|
|
for (i=0; libc.auxv[i] != AT_SYSINFO_EHDR; i+=2)
|
|
if (!libc.auxv[i]) return 0;
|
|
if (!libc.auxv[i+1]) return 0;
|
|
Ehdr *eh = (void *)libc.auxv[i+1];
|
|
Phdr *ph = (void *)((char *)eh + eh->e_phoff);
|
|
size_t *dynv=0, base=-1;
|
|
for (i=0; i<eh->e_phnum; i++, ph=(void *)((char *)ph+eh->e_phentsize)) {
|
|
if (ph->p_type == PT_LOAD)
|
|
base = (size_t)eh + ph->p_offset - ph->p_vaddr;
|
|
else if (ph->p_type == PT_DYNAMIC)
|
|
dynv = (void *)((char *)eh + ph->p_offset);
|
|
}
|
|
if (!dynv || base==(size_t)-1) return 0;
|
|
|
|
char *strings = 0;
|
|
Sym *syms = 0;
|
|
Elf_Symndx *hashtab = 0;
|
|
uint32_t *ghashtab = 0;
|
|
uint16_t *versym = 0;
|
|
Verdef *verdef = 0;
|
|
|
|
for (i=0; dynv[i]; i+=2) {
|
|
void *p = (void *)(base + dynv[i+1]);
|
|
switch(dynv[i]) {
|
|
case DT_STRTAB: strings = p; break;
|
|
case DT_SYMTAB: syms = p; break;
|
|
case DT_HASH: hashtab = p; break;
|
|
case DT_GNU_HASH: ghashtab = p; break;
|
|
case DT_VERSYM: versym = p; break;
|
|
case DT_VERDEF: verdef = p; break;
|
|
}
|
|
}
|
|
|
|
if (!strings || !syms) return 0;
|
|
if (!verdef) versym = 0;
|
|
size_t nsym = 0;
|
|
|
|
if (hashtab) nsym = hashtab[1];
|
|
else if (ghashtab) nsym = count_syms_gnu(ghashtab);
|
|
|
|
for (i=0; i<nsym; i++) {
|
|
if (!(1<<(syms[i].st_info&0xf) & OK_TYPES)) continue;
|
|
if (!(1<<(syms[i].st_info>>4) & OK_BINDS)) continue;
|
|
if (!syms[i].st_shndx) continue;
|
|
if (strcmp(name, strings+syms[i].st_name)) continue;
|
|
if (versym && !checkver(verdef, versym[i], vername, strings))
|
|
continue;
|
|
return (void *)(base + syms[i].st_value);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|