From 3423e3492b178ebe5900d8a68ba6d1dbb02bbcb8 Mon Sep 17 00:00:00 2001 From: Cyrill Gorcunov Date: Mon, 23 Jun 2014 17:00:00 +0400 Subject: [PATCH] zdtm: Add vdso01 test case It parses vDSO in memory (just like CRIU does) and then use direct calls to vDSO entries instead of .plt/.got bundle. The reason for that -- I must be sure we're able to proceed calls without relying on libc anyhow. Note the test is x86-64 specific so I don't turn in on in test suite by default. Signed-off-by: Cyrill Gorcunov Signed-off-by: Pavel Emelyanov --- test/zdtm/live/static/Makefile | 1 + test/zdtm/live/static/vdso01.c | 421 +++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 test/zdtm/live/static/vdso01.c diff --git a/test/zdtm/live/static/Makefile b/test/zdtm/live/static/Makefile index 319e552ec..7dad8e6a4 100644 --- a/test/zdtm/live/static/Makefile +++ b/test/zdtm/live/static/Makefile @@ -45,6 +45,7 @@ TST_NOFILE = \ pthread00 \ pthread01 \ vdso00 \ + vdso01 \ utsname \ pstree \ sockets01 \ diff --git a/test/zdtm/live/static/vdso01.c b/test/zdtm/live/static/vdso01.c new file mode 100644 index 000000000..dd5ee3aa9 --- /dev/null +++ b/test/zdtm/live/static/vdso01.c @@ -0,0 +1,421 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "zdtmtst.h" + +const char *test_doc = "Check if we can use vDSO using direct vDSO calls\n"; +const char *test_author = "Cyrill Gorcunov offset == VDSO_BAD_ADDR && s->name[0] == '\0'; +} + +enum { + VDSO_SYMBOL_CLOCK_GETTIME, + VDSO_SYMBOL_GETCPU, + VDSO_SYMBOL_GETTIMEOFDAY, + VDSO_SYMBOL_TIME, + + VDSO_SYMBOL_MAX +}; + +#define VDSO_SYMBOL_CLOCK_GETTIME_NAME "__vdso_clock_gettime" +#define VDSO_SYMBOL_GETCPU_NAME "__vdso_getcpu" +#define VDSO_SYMBOL_GETTIMEOFDAY_NAME "__vdso_gettimeofday" +#define VDSO_SYMBOL_TIME_NAME "__vdso_time" + +struct vdso_symtable { + unsigned long vma_start; + unsigned long vma_end; + struct vdso_symbol symbols[VDSO_SYMBOL_MAX]; +}; + +#define VDSO_SYMTABLE_INIT \ + { \ + .vma_start = VDSO_BAD_ADDR, \ + .vma_end = VDSO_BAD_ADDR, \ + .symbols = { \ + [0 ... VDSO_SYMBOL_MAX - 1] = \ + (struct vdso_symbol)VDSO_SYMBOL_INIT, \ + }, \ + } + +static bool __ptr_oob(void *ptr, void *start, size_t size) +{ + void *end = (void *)((unsigned long)start + size); + return ptr > end || ptr < start; +} + +static unsigned long elf_hash(const unsigned char *name) +{ + unsigned long h = 0, g; + + while (*name) { + h = (h << 4) + *name++; + g = h & 0xf0000000ul; + if (g) + h ^= g >> 24; + h &= ~g; + } + return h; +} + +static int vdso_fill_symtable(char *mem, size_t size, struct vdso_symtable *t) +{ + Elf64_Phdr *dynamic = NULL, *load = NULL; + Elf64_Ehdr *ehdr = (void *)mem; + Elf64_Dyn *dyn_strtab = NULL; + Elf64_Dyn *dyn_symtab = NULL; + Elf64_Dyn *dyn_strsz = NULL; + Elf64_Dyn *dyn_syment = NULL; + Elf64_Dyn *dyn_hash = NULL; + Elf64_Word *hash = NULL; + Elf64_Phdr *phdr; + Elf64_Dyn *d; + + Elf64_Word *bucket, *chain; + Elf64_Word nbucket, nchain; + + /* + * See Elf specification for this magic values. + */ + const char elf_ident[] = { + 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + const char *vdso_symbols[VDSO_SYMBOL_MAX] = { + [VDSO_SYMBOL_CLOCK_GETTIME] = VDSO_SYMBOL_CLOCK_GETTIME_NAME, + [VDSO_SYMBOL_GETCPU] = VDSO_SYMBOL_GETCPU_NAME, + [VDSO_SYMBOL_GETTIMEOFDAY] = VDSO_SYMBOL_GETTIMEOFDAY_NAME, + [VDSO_SYMBOL_TIME] = VDSO_SYMBOL_TIME_NAME, + }; + + char *dynsymbol_names; + unsigned int i, j, k; + + BUILD_BUG_ON(sizeof(elf_ident) != sizeof(ehdr->e_ident)); + + test_msg("Parsing at %lx %lx\n", (long)mem, (long)mem + (long)size); + + /* + * Make sure it's a file we support. + */ + if (memcmp(ehdr->e_ident, elf_ident, sizeof(elf_ident))) { + err("Elf header magic mismatch\n"); + return -EINVAL; + } + + /* + * We need PT_LOAD and PT_DYNAMIC here. Each once. + */ + phdr = (void *)&mem[ehdr->e_phoff]; + for (i = 0; i < ehdr->e_phnum; i++, phdr++) { + if (__ptr_oob(phdr, mem, size)) + goto err_oob; + switch (phdr->p_type) { + case PT_DYNAMIC: + if (dynamic) { + err("Second PT_DYNAMIC header\n"); + return -EINVAL; + } + dynamic = phdr; + break; + case PT_LOAD: + if (load) { + err("Second PT_LOAD header\n"); + return -EINVAL; + } + load = phdr; + break; + } + } + + if (!load || !dynamic) { + err("One of obligated program headers is missed\n"); + return -EINVAL; + } + + test_msg("PT_LOAD p_vaddr: %lx\n", (unsigned long)load->p_vaddr); + + /* + * Dynamic section tags should provide us the rest of information + * needed. Note that we're interested in a small set of tags. + */ + d = (void *)&mem[dynamic->p_offset]; + for (i = 0; i < dynamic->p_filesz / sizeof(*d); i++, d++) { + if (__ptr_oob(d, mem, size)) + goto err_oob; + + if (d->d_tag == DT_NULL) { + break; + } else if (d->d_tag == DT_STRTAB) { + dyn_strtab = d; + } else if (d->d_tag == DT_SYMTAB) { + dyn_symtab = d; + } else if (d->d_tag == DT_STRSZ) { + dyn_strsz = d; + } else if (d->d_tag == DT_SYMENT) { + dyn_syment = d; + } else if (d->d_tag == DT_HASH) { + dyn_hash = d; + } + } + + if (!dyn_strtab || !dyn_symtab || !dyn_strsz || !dyn_syment || !dyn_hash) { + err("Not all dynamic entries are present\n"); + return -EINVAL; + } + + dynsymbol_names = &mem[dyn_strtab->d_un.d_val - load->p_vaddr]; + if (__ptr_oob(dynsymbol_names, mem, size)) + goto err_oob; + + hash = (void *)&mem[(unsigned long)dyn_hash->d_un.d_ptr - (unsigned long)load->p_vaddr]; + if (__ptr_oob(hash, mem, size)) + goto err_oob; + + nbucket = hash[0]; + nchain = hash[1]; + bucket = &hash[2]; + chain = &hash[nbucket + 2]; + + test_msg("nbucket %lu nchain %lu bucket %p chain %p\n", + (long)nbucket, (long)nchain, bucket, chain); + + for (i = 0; i < ARRAY_SIZE(vdso_symbols); i++) { + k = elf_hash((const unsigned char *)vdso_symbols[i]); + + for (j = bucket[k % nbucket]; j < nchain && chain[j] != STN_UNDEF; j = chain[j]) { + Elf64_Sym *sym = (void *)&mem[dyn_symtab->d_un.d_ptr - load->p_vaddr]; + char *name; + + sym = &sym[j]; + if (__ptr_oob(sym, mem, size)) + continue; + + if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC && + ELF64_ST_BIND(sym->st_info) != STB_GLOBAL) + continue; + + name = &dynsymbol_names[sym->st_name]; + if (__ptr_oob(name, mem, size)) + continue; + + if (strcmp(name, vdso_symbols[i])) + continue; + + memcpy(t->symbols[i].name, name, sizeof(t->symbols[i].name)); + t->symbols[i].offset = (unsigned long)sym->st_value - load->p_vaddr; + test_msg("symbol %s offset %lx\n", t->symbols[i].name, t->symbols[i].offset); + break; + } + } + + return 0; + +err_oob: + err("Corrupted Elf data\n"); + return -EFAULT; +} + +static int vdso_fill_self_symtable(struct vdso_symtable *s) +{ + char buf[512]; + int ret = -1; + FILE *maps; + + *s = (struct vdso_symtable)VDSO_SYMTABLE_INIT; + + maps = fopen("/proc/self/maps", "r"); + if (!maps) { + err("Can't open self-vma"); + return -1; + } + + while (fgets(buf, sizeof(buf), maps)) { + unsigned long start, end; + + if (!strstr(buf, "[vdso]")) + continue; + + ret = sscanf(buf, "%lx-%lx", &start, &end); + if (ret != 2) { + ret = -1; + err("Can't find vDSO bounds"); + goto err; + } + + s->vma_start = start; + s->vma_end = end; + + ret = vdso_fill_symtable((void *)start, end - start, s); + break; + } + + test_msg("[vdso] %lx-%lx\n", s->vma_start, s->vma_end); +err: + fclose(maps); + return ret; +} + +static int vdso_clock_gettime_handler(void *func) +{ + __vdso_clock_gettime_t *vdso_clock_gettime = func; + struct timespec ts1, ts2; + + clock_gettime(CLOCK_REALTIME, &ts1); + vdso_clock_gettime(CLOCK_REALTIME, &ts2); + + test_msg("clock_gettime: tv_sec %li vdso_clock_gettime: tv_sec %li\n", + ts1.tv_sec, ts2.tv_sec); + + if (abs(ts1.tv_sec - ts2.tv_sec) > TIME_DELTA_SEC) { + err("Delta is too big"); + return -1; + } + + return 0; +} + +static int vdso_getcpu_handler(void *func) +{ + __vdso_getcpu_t *vdso_getcpu = func; + unsigned cpu, node; + + vdso_getcpu(&cpu, &node, NULL); + test_msg("vdso_getcpu: cpu %d node %d\n", cpu, node); + + return 0; +} + +static int vdso_gettimeofday_handler(void *func) +{ + __vdso_gettimeofday_t *vdso_gettimeofday = func; + struct timeval tv1, tv2; + struct timezone tz; + + gettimeofday(&tv1, &tz); + vdso_gettimeofday(&tv2, &tz); + + test_msg("gettimeofday: tv_sec %li vdso_gettimeofday: tv_sec %li\n", + tv1.tv_sec, tv2.tv_sec); + + if (abs(tv1.tv_sec - tv2.tv_sec) > TIME_DELTA_SEC) { + err("Delta is too big"); + return -1; + } + + return 0; +} + +static int vdso_time_handler(void *func) +{ + __vdso_time_t *vdso_time = func; + time_t t1, t2; + + t1 = time(NULL); + t2 = vdso_time(NULL); + + test_msg("time: %li vdso_time: %li\n", (long)t1, (long)t1); + + if (abs(t1 - t2) > TIME_DELTA_SEC) { + err("Delta is too big"); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + typedef int (handler_t)(void *func); + + struct vdso_symtable symtable; + size_t i; + + handler_t *handlers[VDSO_SYMBOL_MAX] = { + [VDSO_SYMBOL_CLOCK_GETTIME] = vdso_clock_gettime_handler, + [VDSO_SYMBOL_GETCPU] = vdso_getcpu_handler, + [VDSO_SYMBOL_GETTIMEOFDAY] = vdso_gettimeofday_handler, + [VDSO_SYMBOL_TIME] = vdso_time_handler, + }; + + test_init(argc, argv); + + if (vdso_fill_self_symtable(&symtable)) { + err("Faied to parse vdso\n"); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(symtable.symbols); i++) { + struct vdso_symbol *s = &symtable.symbols[i]; + handler_t *func; + + if (vdso_symbol_empty(s) || i > ARRAY_SIZE(handlers)) + continue; + func = handlers[i]; + + if (func((void *)(s->offset + symtable.vma_start))) { + err("Handler error"); + return -1; + } + } + + test_daemon(); + test_waitsig(); + + /* + * After restore the vDSO must remain in old place. + */ + for (i = 0; i < ARRAY_SIZE(symtable.symbols); i++) { + struct vdso_symbol *s = &symtable.symbols[i]; + handler_t *func; + + if (vdso_symbol_empty(s) || i > ARRAY_SIZE(handlers)) + continue; + func = handlers[i]; + + if (func((void *)(s->offset + symtable.vma_start))) { + fail("Handler error"); + return -1; + } + } + + pass(); + + return 0; +}