Andy Lutomirski | 98eedc3 | 2011-07-13 09:24:16 -0400 | [diff] [blame] | 1 | /* |
| 2 | * vdso_test.c: Sample code to test parse_vdso.c on x86_64 |
| 3 | * Copyright (c) 2011 Andy Lutomirski |
| 4 | * Subject to the GNU General Public License, version 2 |
| 5 | * |
| 6 | * You can amuse yourself by compiling with: |
| 7 | * gcc -std=gnu99 -nostdlib |
| 8 | * -Os -fno-asynchronous-unwind-tables -flto |
| 9 | * vdso_test.c parse_vdso.c -o vdso_test |
| 10 | * to generate a small binary with no dependencies at all. |
| 11 | */ |
| 12 | |
| 13 | #include <sys/syscall.h> |
| 14 | #include <sys/time.h> |
| 15 | #include <unistd.h> |
| 16 | #include <stdint.h> |
| 17 | |
| 18 | extern void *vdso_sym(const char *version, const char *name); |
| 19 | extern void vdso_init_from_sysinfo_ehdr(uintptr_t base); |
| 20 | extern void vdso_init_from_auxv(void *auxv); |
| 21 | |
| 22 | /* We need a libc functions... */ |
| 23 | int strcmp(const char *a, const char *b) |
| 24 | { |
| 25 | /* This implementation is buggy: it never returns -1. */ |
| 26 | while (*a || *b) { |
| 27 | if (*a != *b) |
| 28 | return 1; |
| 29 | if (*a == 0 || *b == 0) |
| 30 | return 1; |
| 31 | a++; |
| 32 | b++; |
| 33 | } |
| 34 | |
| 35 | return 0; |
| 36 | } |
| 37 | |
| 38 | /* ...and two syscalls. This is x86_64-specific. */ |
| 39 | static inline long linux_write(int fd, const void *data, size_t len) |
| 40 | { |
| 41 | |
| 42 | long ret; |
| 43 | asm volatile ("syscall" : "=a" (ret) : "a" (__NR_write), |
| 44 | "D" (fd), "S" (data), "d" (len) : |
| 45 | "cc", "memory", "rcx", |
| 46 | "r8", "r9", "r10", "r11" ); |
| 47 | return ret; |
| 48 | } |
| 49 | |
| 50 | static inline void linux_exit(int code) |
| 51 | { |
| 52 | asm volatile ("syscall" : : "a" (__NR_exit), "D" (code)); |
| 53 | } |
| 54 | |
| 55 | void to_base10(char *lastdig, uint64_t n) |
| 56 | { |
| 57 | while (n) { |
| 58 | *lastdig = (n % 10) + '0'; |
| 59 | n /= 10; |
| 60 | lastdig--; |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | __attribute__((externally_visible)) void c_main(void **stack) |
| 65 | { |
| 66 | /* Parse the stack */ |
| 67 | long argc = (long)*stack; |
| 68 | stack += argc + 2; |
| 69 | |
| 70 | /* Now we're pointing at the environment. Skip it. */ |
| 71 | while(*stack) |
| 72 | stack++; |
| 73 | stack++; |
| 74 | |
| 75 | /* Now we're pointing at auxv. Initialize the vDSO parser. */ |
| 76 | vdso_init_from_auxv((void *)stack); |
| 77 | |
| 78 | /* Find gettimeofday. */ |
| 79 | typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz); |
| 80 | gtod_t gtod = (gtod_t)vdso_sym("LINUX_2.6", "__vdso_gettimeofday"); |
| 81 | |
| 82 | if (!gtod) |
| 83 | linux_exit(1); |
| 84 | |
| 85 | struct timeval tv; |
| 86 | long ret = gtod(&tv, 0); |
| 87 | |
| 88 | if (ret == 0) { |
| 89 | char buf[] = "The time is .000000\n"; |
| 90 | to_base10(buf + 31, tv.tv_sec); |
| 91 | to_base10(buf + 38, tv.tv_usec); |
| 92 | linux_write(1, buf, sizeof(buf) - 1); |
| 93 | } else { |
| 94 | linux_exit(ret); |
| 95 | } |
| 96 | |
| 97 | linux_exit(0); |
| 98 | } |
| 99 | |
| 100 | /* |
| 101 | * This is the real entry point. It passes the initial stack into |
| 102 | * the C entry point. |
| 103 | */ |
| 104 | asm ( |
| 105 | ".text\n" |
| 106 | ".global _start\n" |
| 107 | ".type _start,@function\n" |
| 108 | "_start:\n\t" |
| 109 | "mov %rsp,%rdi\n\t" |
| 110 | "jmp c_main" |
| 111 | ); |