linux/tools/testing/selftests/x86/test_vdso.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * ldt_gdt.c - Test cases for LDT and GDT access
   4 * Copyright (c) 2011-2015 Andrew Lutomirski
   5 */
   6
   7#define _GNU_SOURCE
   8
   9#include <stdio.h>
  10#include <sys/time.h>
  11#include <time.h>
  12#include <stdlib.h>
  13#include <unistd.h>
  14#include <sys/syscall.h>
  15#include <dlfcn.h>
  16#include <string.h>
  17#include <errno.h>
  18#include <sched.h>
  19#include <stdbool.h>
  20#include <limits.h>
  21
  22#ifndef SYS_getcpu
  23# ifdef __x86_64__
  24#  define SYS_getcpu 309
  25# else
  26#  define SYS_getcpu 318
  27# endif
  28#endif
  29
  30/* max length of lines in /proc/self/maps - anything longer is skipped here */
  31#define MAPS_LINE_LEN 128
  32
  33int nerrs = 0;
  34
  35typedef int (*vgettime_t)(clockid_t, struct timespec *);
  36
  37vgettime_t vdso_clock_gettime;
  38
  39typedef long (*vgtod_t)(struct timeval *tv, struct timezone *tz);
  40
  41vgtod_t vdso_gettimeofday;
  42
  43typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
  44
  45getcpu_t vgetcpu;
  46getcpu_t vdso_getcpu;
  47
  48static void *vsyscall_getcpu(void)
  49{
  50#ifdef __x86_64__
  51        FILE *maps;
  52        char line[MAPS_LINE_LEN];
  53        bool found = false;
  54
  55        maps = fopen("/proc/self/maps", "r");
  56        if (!maps) /* might still be present, but ignore it here, as we test vDSO not vsyscall */
  57                return NULL;
  58
  59        while (fgets(line, MAPS_LINE_LEN, maps)) {
  60                char r, x;
  61                void *start, *end;
  62                char name[MAPS_LINE_LEN];
  63
  64                /* sscanf() is safe here as strlen(name) >= strlen(line) */
  65                if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
  66                           &start, &end, &r, &x, name) != 5)
  67                        continue;
  68
  69                if (strcmp(name, "[vsyscall]"))
  70                        continue;
  71
  72                /* assume entries are OK, as we test vDSO here not vsyscall */
  73                found = true;
  74                break;
  75        }
  76
  77        fclose(maps);
  78
  79        if (!found) {
  80                printf("Warning: failed to find vsyscall getcpu\n");
  81                return NULL;
  82        }
  83        return (void *) (0xffffffffff600800);
  84#else
  85        return NULL;
  86#endif
  87}
  88
  89
  90static void fill_function_pointers()
  91{
  92        void *vdso = dlopen("linux-vdso.so.1",
  93                            RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
  94        if (!vdso)
  95                vdso = dlopen("linux-gate.so.1",
  96                              RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
  97        if (!vdso) {
  98                printf("[WARN]\tfailed to find vDSO\n");
  99                return;
 100        }
 101
 102        vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
 103        if (!vdso_getcpu)
 104                printf("Warning: failed to find getcpu in vDSO\n");
 105
 106        vgetcpu = (getcpu_t) vsyscall_getcpu();
 107
 108        vdso_clock_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
 109        if (!vdso_clock_gettime)
 110                printf("Warning: failed to find clock_gettime in vDSO\n");
 111
 112        vdso_gettimeofday = (vgtod_t)dlsym(vdso, "__vdso_gettimeofday");
 113        if (!vdso_gettimeofday)
 114                printf("Warning: failed to find gettimeofday in vDSO\n");
 115
 116}
 117
 118static long sys_getcpu(unsigned * cpu, unsigned * node,
 119                       void* cache)
 120{
 121        return syscall(__NR_getcpu, cpu, node, cache);
 122}
 123
 124static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
 125{
 126        return syscall(__NR_clock_gettime, id, ts);
 127}
 128
 129static inline int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
 130{
 131        return syscall(__NR_gettimeofday, tv, tz);
 132}
 133
 134static void test_getcpu(void)
 135{
 136        printf("[RUN]\tTesting getcpu...\n");
 137
 138        for (int cpu = 0; ; cpu++) {
 139                cpu_set_t cpuset;
 140                CPU_ZERO(&cpuset);
 141                CPU_SET(cpu, &cpuset);
 142                if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
 143                        return;
 144
 145                unsigned cpu_sys, cpu_vdso, cpu_vsys,
 146                        node_sys, node_vdso, node_vsys;
 147                long ret_sys, ret_vdso = 1, ret_vsys = 1;
 148                unsigned node;
 149
 150                ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
 151                if (vdso_getcpu)
 152                        ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
 153                if (vgetcpu)
 154                        ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
 155
 156                if (!ret_sys)
 157                        node = node_sys;
 158                else if (!ret_vdso)
 159                        node = node_vdso;
 160                else if (!ret_vsys)
 161                        node = node_vsys;
 162
 163                bool ok = true;
 164                if (!ret_sys && (cpu_sys != cpu || node_sys != node))
 165                        ok = false;
 166                if (!ret_vdso && (cpu_vdso != cpu || node_vdso != node))
 167                        ok = false;
 168                if (!ret_vsys && (cpu_vsys != cpu || node_vsys != node))
 169                        ok = false;
 170
 171                printf("[%s]\tCPU %u:", ok ? "OK" : "FAIL", cpu);
 172                if (!ret_sys)
 173                        printf(" syscall: cpu %u, node %u", cpu_sys, node_sys);
 174                if (!ret_vdso)
 175                        printf(" vdso: cpu %u, node %u", cpu_vdso, node_vdso);
 176                if (!ret_vsys)
 177                        printf(" vsyscall: cpu %u, node %u", cpu_vsys,
 178                               node_vsys);
 179                printf("\n");
 180
 181                if (!ok)
 182                        nerrs++;
 183        }
 184}
 185
 186static bool ts_leq(const struct timespec *a, const struct timespec *b)
 187{
 188        if (a->tv_sec != b->tv_sec)
 189                return a->tv_sec < b->tv_sec;
 190        else
 191                return a->tv_nsec <= b->tv_nsec;
 192}
 193
 194static bool tv_leq(const struct timeval *a, const struct timeval *b)
 195{
 196        if (a->tv_sec != b->tv_sec)
 197                return a->tv_sec < b->tv_sec;
 198        else
 199                return a->tv_usec <= b->tv_usec;
 200}
 201
 202static char const * const clocknames[] = {
 203        [0] = "CLOCK_REALTIME",
 204        [1] = "CLOCK_MONOTONIC",
 205        [2] = "CLOCK_PROCESS_CPUTIME_ID",
 206        [3] = "CLOCK_THREAD_CPUTIME_ID",
 207        [4] = "CLOCK_MONOTONIC_RAW",
 208        [5] = "CLOCK_REALTIME_COARSE",
 209        [6] = "CLOCK_MONOTONIC_COARSE",
 210        [7] = "CLOCK_BOOTTIME",
 211        [8] = "CLOCK_REALTIME_ALARM",
 212        [9] = "CLOCK_BOOTTIME_ALARM",
 213        [10] = "CLOCK_SGI_CYCLE",
 214        [11] = "CLOCK_TAI",
 215};
 216
 217static void test_one_clock_gettime(int clock, const char *name)
 218{
 219        struct timespec start, vdso, end;
 220        int vdso_ret, end_ret;
 221
 222        printf("[RUN]\tTesting clock_gettime for clock %s (%d)...\n", name, clock);
 223
 224        if (sys_clock_gettime(clock, &start) < 0) {
 225                if (errno == EINVAL) {
 226                        vdso_ret = vdso_clock_gettime(clock, &vdso);
 227                        if (vdso_ret == -EINVAL) {
 228                                printf("[OK]\tNo such clock.\n");
 229                        } else {
 230                                printf("[FAIL]\tNo such clock, but __vdso_clock_gettime returned %d\n", vdso_ret);
 231                                nerrs++;
 232                        }
 233                } else {
 234                        printf("[WARN]\t clock_gettime(%d) syscall returned error %d\n", clock, errno);
 235                }
 236                return;
 237        }
 238
 239        vdso_ret = vdso_clock_gettime(clock, &vdso);
 240        end_ret = sys_clock_gettime(clock, &end);
 241
 242        if (vdso_ret != 0 || end_ret != 0) {
 243                printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
 244                       vdso_ret, errno);
 245                nerrs++;
 246                return;
 247        }
 248
 249        printf("\t%llu.%09ld %llu.%09ld %llu.%09ld\n",
 250               (unsigned long long)start.tv_sec, start.tv_nsec,
 251               (unsigned long long)vdso.tv_sec, vdso.tv_nsec,
 252               (unsigned long long)end.tv_sec, end.tv_nsec);
 253
 254        if (!ts_leq(&start, &vdso) || !ts_leq(&vdso, &end)) {
 255                printf("[FAIL]\tTimes are out of sequence\n");
 256                nerrs++;
 257        }
 258}
 259
 260static void test_clock_gettime(void)
 261{
 262        for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]);
 263             clock++) {
 264                test_one_clock_gettime(clock, clocknames[clock]);
 265        }
 266
 267        /* Also test some invalid clock ids */
 268        test_one_clock_gettime(-1, "invalid");
 269        test_one_clock_gettime(INT_MIN, "invalid");
 270        test_one_clock_gettime(INT_MAX, "invalid");
 271}
 272
 273static void test_gettimeofday(void)
 274{
 275        struct timeval start, vdso, end;
 276        struct timezone sys_tz, vdso_tz;
 277        int vdso_ret, end_ret;
 278
 279        if (!vdso_gettimeofday)
 280                return;
 281
 282        printf("[RUN]\tTesting gettimeofday...\n");
 283
 284        if (sys_gettimeofday(&start, &sys_tz) < 0) {
 285                printf("[FAIL]\tsys_gettimeofday failed (%d)\n", errno);
 286                nerrs++;
 287                return;
 288        }
 289
 290        vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz);
 291        end_ret = sys_gettimeofday(&end, NULL);
 292
 293        if (vdso_ret != 0 || end_ret != 0) {
 294                printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
 295                       vdso_ret, errno);
 296                nerrs++;
 297                return;
 298        }
 299
 300        printf("\t%llu.%06ld %llu.%06ld %llu.%06ld\n",
 301               (unsigned long long)start.tv_sec, start.tv_usec,
 302               (unsigned long long)vdso.tv_sec, vdso.tv_usec,
 303               (unsigned long long)end.tv_sec, end.tv_usec);
 304
 305        if (!tv_leq(&start, &vdso) || !tv_leq(&vdso, &end)) {
 306                printf("[FAIL]\tTimes are out of sequence\n");
 307                nerrs++;
 308        }
 309
 310        if (sys_tz.tz_minuteswest == vdso_tz.tz_minuteswest &&
 311            sys_tz.tz_dsttime == vdso_tz.tz_dsttime) {
 312                printf("[OK]\ttimezones match: minuteswest=%d, dsttime=%d\n",
 313                       sys_tz.tz_minuteswest, sys_tz.tz_dsttime);
 314        } else {
 315                printf("[FAIL]\ttimezones do not match\n");
 316                nerrs++;
 317        }
 318
 319        /* And make sure that passing NULL for tz doesn't crash. */
 320        vdso_gettimeofday(&vdso, NULL);
 321}
 322
 323int main(int argc, char **argv)
 324{
 325        fill_function_pointers();
 326
 327        test_clock_gettime();
 328        test_gettimeofday();
 329
 330        /*
 331         * Test getcpu() last so that, if something goes wrong setting affinity,
 332         * we still run the other tests.
 333         */
 334        test_getcpu();
 335
 336        return nerrs ? 1 : 0;
 337}
 338