| #define _POSIX_SOURCE |
| |
| #include <stdio.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <stdlib.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| #include <dlfcn.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| #include <sys/ucontext.h> |
| #include <asm/ldt.h> |
| #include <errno.h> |
| |
| static inline int modify_ldt(int mode, void *ptr, unsigned long size) |
| { |
| int ret = syscall(__NR_modify_ldt, mode, ptr, size); |
| if (ret != 0) |
| errno = -ret; |
| return (ret == 0 ? 0 : -1); |
| } |
| |
| #ifdef __x86_64__ |
| # define VSYS(x) (x) |
| #else |
| # define VSYS(x) 0 |
| #endif |
| |
| /* vsyscalls and vDSO */ |
| typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz); |
| const gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000); |
| gtod_t vdso_gtod; |
| |
| typedef int (*vgettime_t)(clockid_t, timespec *); |
| vgettime_t vdso_gettime; |
| |
| typedef long (*time_func_t)(time_t *t); |
| const time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400); |
| time_func_t vdso_time; |
| |
| typedef long (*getcpu_t)(unsigned *, unsigned *, struct getcpu_cache*); |
| const getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800); |
| getcpu_t vdso_getcpu; |
| |
| void init_vdso() |
| { |
| void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); |
| if (!vdso) |
| vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); |
| if (!vdso) { |
| printf("Warning: failed to find vDSO\n"); |
| return; |
| } |
| |
| vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday"); |
| if (!vdso_gtod) |
| printf("Warning: failed to find gettimeofday in vDSO\n"); |
| |
| vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime"); |
| if (!vdso_gettime) |
| printf("Warning: failed to find clock_gettime in vDSO\n"); |
| |
| vdso_time = (time_func_t)dlsym(vdso, "__vdso_time"); |
| if (!vdso_time) |
| printf("Warning: failed to find time in vDSO\n"); |
| |
| vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu"); |
| if (!vdso_getcpu) |
| printf("Warning: failed to find getcpu in vDSO\n"); |
| } |
| |
| /* syscalls */ |
| static inline long sys_gtod(struct timeval *tv, struct timezone *tz) |
| { |
| return syscall(__NR_gettimeofday, tv, tz); |
| } |
| |
| static inline int sys_clock_gettime(clockid_t id, struct timespec *ts) |
| { |
| return syscall(__NR_clock_gettime, id, ts); |
| } |
| |
| static inline long sys_time(time_t *t) |
| { |
| return syscall(__NR_time, t); |
| } |
| |
| static inline long sys_getcpu(unsigned * cpu, unsigned * node, |
| struct getcpu_cache* cache) |
| { |
| #ifdef __NR_getcpu |
| return syscall(__NR_getcpu, cpu, node, cache); |
| #else |
| return -ENOSYS; |
| #endif |
| } |
| |
| static void segv(int sig, siginfo_t *info, void *ctx_void) |
| { |
| psiginfo(info, "Caught SIGSEGV"); |
| |
| #ifdef REG_RIP |
| ucontext_t *ctx = (ucontext_t*)ctx_void; |
| printf("RIP = %lx\n", (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); |
| #endif |
| |
| exit(1); |
| } |
| |
| |
| /* benchmark helper */ |
| template<typename Func> void benchmark(const char *desc, Func f) |
| { |
| struct timespec start, end; |
| long loops = 0; |
| |
| printf("Benchmarking %s ... ", desc); |
| fflush(stdout); |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &start)) { |
| perror("clock_gettime"); |
| exit(1); |
| } |
| |
| while(true) |
| { |
| long loops_now = 1000; |
| for(int i = 0; i < loops_now; i++) |
| f(); |
| loops += loops_now; |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &end)) { |
| perror("clock_gettime"); |
| exit(1); |
| } |
| |
| unsigned long long duration = (end.tv_nsec - start.tv_nsec) + |
| 1000000000ULL * (end.tv_sec - start.tv_sec); |
| |
| if (duration < 500000000ULL) |
| continue; |
| |
| printf("%9ld loops in %.5fs = %7.2f nsec / loop\n", |
| loops, float(duration) * 1e-9, |
| float(duration) / loops); |
| break; |
| } |
| } |
| |
| static double tv_diff(const struct timeval &a, const struct timeval &b) |
| { |
| return double(a.tv_sec - b.tv_sec) + |
| double((int)a.tv_usec - (int)b.tv_usec) * 1e-6; |
| } |
| |
| int test(int argc, char **argv) |
| { |
| printf("Testing gettimeofday...\n"); |
| struct timeval tv_sys, tv_vdso, tv_vsys; |
| struct timezone tz_sys, tz_vdso, tz_vsys; |
| int ret_sys = sys_gtod(&tv_sys, &tz_sys); |
| int ret_vdso = -1; |
| if (vdso_gtod) |
| ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso); |
| int ret_vsys = -1; |
| if (vgtod) |
| vgtod(&tv_vsys, &tz_vsys); |
| |
| if (ret_sys) { |
| printf(" syscall failed\n"); |
| } else { |
| if (ret_vdso == 0) { |
| if (tz_sys.tz_minuteswest != tz_vdso.tz_minuteswest || tz_sys.tz_dsttime != tz_vdso.tz_dsttime) |
| printf(" vDSO tz mismatch\n"); |
| else |
| printf(" vDSO offset = %.6fs\n", tv_diff(tv_vdso, tv_sys)); |
| } else if (vdso_gtod) { |
| printf(" vDSO failed\n"); |
| } |
| if (ret_vsys == 0) { |
| if (tz_sys.tz_minuteswest != tz_vsys.tz_minuteswest || tz_sys.tz_dsttime != tz_vsys.tz_dsttime) |
| printf(" vsyscall tz mismatch\n"); |
| else |
| printf(" vsyscall offset = %.6fs\n", tv_diff(tv_vsys, tv_sys)); |
| } |
| } |
| |
| printf("\nTesting time...\n"); |
| long t_sys, t_vdso = 0, t_vsys; |
| long t2_sys = -1, t2_vdso = -1, t2_vsys = -1; |
| t_sys = sys_time(&t2_sys); |
| if (vdso_time) |
| t_vdso = vdso_time(&t2_vdso); |
| if (vtime) |
| t_vsys = vtime(&t2_vsys); |
| if (t_sys < 0 || t_sys != t2_sys) { |
| printf(" syscall failed (ret:%ld output:%ld)\n", t_sys, t2_sys); |
| } else { |
| if (vdso_time) { |
| if (t_vdso < 0 || t_vdso != t2_vdso) |
| printf(" vDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso); |
| else |
| printf(" vDSO offset = %ld\n", t_vdso - t_sys); |
| } |
| |
| if (vtime) { |
| if (t_vsys < 0 || t_vsys != t2_vsys) |
| printf(" vsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys); |
| else |
| printf(" vsyscall offset = %ld\n", t_vsys - t_sys); |
| } |
| } |
| |
| printf("Testing getcpu...\n"); |
| unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys; |
| ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0); |
| if (vdso_getcpu) |
| ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0); |
| if (vgetcpu) |
| ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0); |
| if (!ret_sys) |
| printf(" syscall: cpu=%u node=%u\n", cpu_sys, node_sys); |
| if (ret_vdso && vdso_getcpu) |
| printf(" vDSO failed (ret:%ld)\n", (unsigned long)ret_vdso); |
| if (ret_vsys) |
| printf(" vsyscall failed (ret:%ld)\n", (unsigned long)ret_vdso); |
| if (ret_vdso == 0 && ret_vsys == 0) { |
| if (vdso_getcpu && cpu_vdso != cpu_vsys) |
| printf(" cpu mismatch (vdso:%u vsyscall:%u)!\n", cpu_vdso, cpu_vsys); |
| else if (node_vdso != node_vsys) |
| printf(" node mismatch (vdso:%u vsyscall:%u)!\n", node_vdso, node_vsys); |
| else |
| printf(" ok! cpu=%u node=%u\n", cpu_vdso, node_vdso); |
| } |
| |
| return 0; |
| } |
| |
| int bench(int argc, char **argv) |
| { |
| struct timeval tv; |
| struct timezone tz; |
| struct timespec ts; |
| |
| benchmark(" syscall gettimeofday ", [&]{sys_gtod(&tv, &tz);}); |
| benchmark(" vdso gettimeofday ", [&]{vdso_gtod(&tv, &tz);}); |
| benchmark("vsyscall gettimeofday ", [&]{vgtod(&tv, &tz);}); |
| |
| printf("\n"); |
| benchmark(" syscall CLOCK_MONOTONIC ", [&]{ |
| sys_clock_gettime(CLOCK_MONOTONIC, &ts); |
| }); |
| benchmark(" vdso CLOCK_MONOTONIC ", [&]{vdso_gettime(CLOCK_MONOTONIC, &ts);}); |
| |
| printf("\n"); |
| time_t t; |
| benchmark(" syscall time ", [&]{sys_time(&t);}); |
| if (vdso_time) |
| benchmark(" vdso time ", [&]{vdso_time(&t);}); |
| benchmark("vsyscall time ", [&]{vtime(&t);}); |
| |
| printf("\n"); |
| unsigned cpu, node; |
| benchmark(" vdso getcpu ", [&]{vdso_getcpu(&cpu, &node, 0);}); |
| benchmark("vsyscall getcpu ", [&]{vgetcpu(&cpu, &node, 0);}); |
| |
| printf("\n"); |
| benchmark("dummy syscall ", [&]{syscall(0xffffffff);}); |
| |
| return 0; |
| } |
| |
| int call(int argc, char **argv) |
| { |
| if (argc != 5) { |
| printf("Usage: call <addr> <rax> <arg1> <arg2> <arg3>\n"); |
| return 1; |
| } |
| |
| unsigned long addr, rax, arg1, arg2, arg3; |
| char *end; |
| addr = strtoull(argv[0], &end, 0); |
| if (*end) |
| goto bad; |
| |
| rax = strtoull(argv[1], &end, 0); |
| if (*end) |
| goto bad; |
| |
| arg1 = strtoull(argv[2], &end, 0); |
| if (*end) |
| goto bad; |
| |
| arg2 = strtoull(argv[3], &end, 0); |
| if (*end) |
| goto bad; |
| |
| arg3 = strtoull(argv[4], &end, 0); |
| if (*end) |
| goto bad; |
| |
| unsigned long ret; |
| asm volatile("call *%[addr]" : "=a" (ret) : [addr] "rm" (addr), "a" (rax), |
| "D" (arg1), "S" (arg2), "d" (arg3)); |
| printf("Return value = %ld\n", ret); |
| |
| return 0; |
| |
| bad: |
| printf("Bad arg\n"); |
| return 1; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| struct sigaction sa_segv; |
| memset(&sa_segv, 0, sizeof(sa_segv)); |
| sa_segv.sa_sigaction = segv; |
| sa_segv.sa_flags = SA_SIGINFO; |
| sigemptyset(&sa_segv.sa_mask); |
| if (sigaction(SIGSEGV, &sa_segv, 0)) |
| perror("sigaction"); |
| |
| init_vdso(); |
| if (argc < 2) { |
| printf("Usage: test_vsyscall <command> ...\n" |
| "command := { test, bench, int, call }\n"); |
| return 1; |
| } |
| |
| if (!strcmp(argv[1], "test")) |
| return test(argc - 2, argv + 2); |
| if (!strcmp(argv[1], "bench")) |
| return bench(argc - 2, argv + 2); |
| if (!strcmp(argv[1], "call")) |
| return call(argc - 2, argv + 2); |
| |
| printf("Unknown command\n"); |
| return 1; |
| } |