add working vdso clock_gettime support, including static linking
authorRich Felker <dalias@aerifal.cx>
Wed, 16 Apr 2014 06:33:29 +0000 (02:33 -0400)
committerRich Felker <dalias@aerifal.cx>
Wed, 16 Apr 2014 06:33:29 +0000 (02:33 -0400)
the vdso symbol lookup code is based on the original 2011 patch by
Nicholas J. Kain, with some streamlining, pointer arithmetic fixes,
and one symbol version matching fix.

on the consumer side (clock_gettime), per-arch macros for the
particular symbol name and version to lookup are added in
syscall_arch.h, and no vdso code is pulled in on archs which do not
define these macros. at this time, vdso is enabled only on x86_64.

the vdso support at the dynamic linker level is no longer useful to
libc, but is left in place for the sake of debuggers (which may need
the vdso in the link map to find its functions) and possibly use with
dlsym.

arch/x86_64/syscall_arch.h
src/internal/vdso.c [new file with mode: 0644]
src/time/clock_gettime.c

index a85c440c2769faa3a354f83bc5e1706e61f15844..a7a7b5a65b93bad6373f367f68ebd4ed537c1bd1 100644 (file)
@@ -60,3 +60,7 @@ static __inline long __syscall6(long n, long a1, long a2, long a3, long a4, long
                                                  "d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory");
        return ret;
 }
+
+#define VDSO_USEFUL
+#define VDSO_CGT_SYM "__vdso_clock_gettime"
+#define VDSO_CGT_VER "LINUX_2.6"
diff --git a/src/internal/vdso.c b/src/internal/vdso.c
new file mode 100644 (file)
index 0000000..6ae0212
--- /dev/null
@@ -0,0 +1,91 @@
+#include <elf.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)
+
+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;
+       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;
+       uint32_t *hashtab = 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_VERSYM: versym = p; break;
+               case DT_VERDEF: verdef = p; break;
+               }
+       }       
+
+       if (!strings || !syms || !hashtab) return 0;
+       if (!verdef) versym = 0;
+
+       for (i=0; i<hashtab[1]; 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
index ce9f22090a8eff4be06e01b8769fce098a79a49e..799251d8a7c7cab612dc620fb0ceabd10797b4c2 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include "syscall.h"
 #include "libc.h"
+#include "atomic.h"
 
 static int sc_clock_gettime(clockid_t clk, struct timespec *ts)
 {
@@ -20,14 +21,21 @@ static int sc_clock_gettime(clockid_t clk, struct timespec *ts)
        return -1;
 }
 
-weak_alias(sc_clock_gettime, __vdso_clock_gettime);
-
-int (*__cgt)(clockid_t, struct timespec *) = __vdso_clock_gettime;
+void *__vdsosym(const char *, const char *);
 
 int __clock_gettime(clockid_t clk, struct timespec *ts)
 {
-       /* Conditional is to make this work prior to dynamic linking */
-       return __cgt ? __cgt(clk, ts) : sc_clock_gettime(clk, ts);
+#ifdef VDSO_CGT_SYM
+       static int (*cgt)(clockid_t, struct timespec *);
+       if (!cgt) {
+               void *f = __vdsosym(VDSO_CGT_VER, VDSO_CGT_SYM);
+               if (!f) f = (void *)sc_clock_gettime;
+               a_cas_p(&cgt, 0, f);
+       }
+       return cgt(clk, ts);
+#else
+       return sc_clock_gettime(clk, ts);
+#endif
 }
 
 weak_alias(__clock_gettime, clock_gettime);