linux/tools/testing/selftests/vDSO/vdso_test_correctness.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#include "vdso_config.h"
  23
  24static const char **name;
  25
  26#ifndef SYS_getcpu
  27# ifdef __x86_64__
  28#  define SYS_getcpu 309
  29# else
  30#  define SYS_getcpu 318
  31# endif
  32#endif
  33
  34#ifndef __NR_clock_gettime64
  35#define __NR_clock_gettime64    403
  36#endif
  37
  38#ifndef __kernel_timespec
  39struct __kernel_timespec {
  40        long long       tv_sec;
  41        long long       tv_nsec;
  42};
  43#endif
  44
  45/* max length of lines in /proc/self/maps - anything longer is skipped here */
  46#define MAPS_LINE_LEN 128
  47
  48int nerrs = 0;
  49
  50typedef int (*vgettime_t)(clockid_t, struct timespec *);
  51
  52vgettime_t vdso_clock_gettime;
  53
  54typedef int (*vgettime64_t)(clockid_t, struct __kernel_timespec *);
  55
  56vgettime64_t vdso_clock_gettime64;
  57
  58typedef long (*vgtod_t)(struct timeval *tv, struct timezone *tz);
  59
  60vgtod_t vdso_gettimeofday;
  61
  62typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
  63
  64getcpu_t vgetcpu;
  65getcpu_t vdso_getcpu;
  66
  67static void *vsyscall_getcpu(void)
  68{
  69#ifdef __x86_64__
  70        FILE *maps;
  71        char line[MAPS_LINE_LEN];
  72        bool found = false;
  73
  74        maps = fopen("/proc/self/maps", "r");
  75        if (!maps) /* might still be present, but ignore it here, as we test vDSO not vsyscall */
  76                return NULL;
  77
  78        while (fgets(line, MAPS_LINE_LEN, maps)) {
  79                char r, x;
  80                void *start, *end;
  81                char name[MAPS_LINE_LEN];
  82
  83                /* sscanf() is safe here as strlen(name) >= strlen(line) */
  84                if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
  85                           &start, &end, &r, &x, name) != 5)
  86                        continue;
  87
  88                if (strcmp(name, "[vsyscall]"))
  89                        continue;
  90
  91                /* assume entries are OK, as we test vDSO here not vsyscall */
  92                found = true;
  93                break;
  94        }
  95
  96        fclose(maps);
  97
  98        if (!found) {
  99                printf("Warning: failed to find vsyscall getcpu\n");
 100                return NULL;
 101        }
 102        return (void *) (0xffffffffff600800);
 103#else
 104        return NULL;
 105#endif
 106}
 107
 108
 109static void fill_function_pointers()
 110{
 111        void *vdso = dlopen("linux-vdso.so.1",
 112                            RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
 113        if (!vdso)
 114                vdso = dlopen("linux-gate.so.1",
 115                              RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
 116        if (!vdso) {
 117                printf("[WARN]\tfailed to find vDSO\n");
 118                return;
 119        }
 120
 121        vdso_getcpu = (getcpu_t)dlsym(vdso, name[4]);
 122        if (!vdso_getcpu)
 123                printf("Warning: failed to find getcpu in vDSO\n");
 124
 125        vgetcpu = (getcpu_t) vsyscall_getcpu();
 126
 127        vdso_clock_gettime = (vgettime_t)dlsym(vdso, name[1]);
 128        if (!vdso_clock_gettime)
 129                printf("Warning: failed to find clock_gettime in vDSO\n");
 130
 131#if defined(VDSO_32BIT)
 132        vdso_clock_gettime64 = (vgettime64_t)dlsym(vdso, name[5]);
 133        if (!vdso_clock_gettime64)
 134                printf("Warning: failed to find clock_gettime64 in vDSO\n");
 135#endif
 136
 137        vdso_gettimeofday = (vgtod_t)dlsym(vdso, name[0]);
 138        if (!vdso_gettimeofday)
 139                printf("Warning: failed to find gettimeofday in vDSO\n");
 140
 141}
 142
 143static long sys_getcpu(unsigned * cpu, unsigned * node,
 144                       void* cache)
 145{
 146        return syscall(__NR_getcpu, cpu, node, cache);
 147}
 148
 149static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
 150{
 151        return syscall(__NR_clock_gettime, id, ts);
 152}
 153
 154static inline int sys_clock_gettime64(clockid_t id, struct __kernel_timespec *ts)
 155{
 156        return syscall(__NR_clock_gettime64, id, ts);
 157}
 158
 159static inline int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
 160{
 161        return syscall(__NR_gettimeofday, tv, tz);
 162}
 163
 164static void test_getcpu(void)
 165{
 166        printf("[RUN]\tTesting getcpu...\n");
 167
 168        for (int cpu = 0; ; cpu++) {
 169                cpu_set_t cpuset;
 170                CPU_ZERO(&cpuset);
 171                CPU_SET(cpu, &cpuset);
 172                if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
 173                        return;
 174
 175                unsigned cpu_sys, cpu_vdso, cpu_vsys,
 176                        node_sys, node_vdso, node_vsys;
 177                long ret_sys, ret_vdso = 1, ret_vsys = 1;
 178                unsigned node;
 179
 180                ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
 181                if (vdso_getcpu)
 182                        ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
 183                if (vgetcpu)
 184                        ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
 185
 186                if (!ret_sys)
 187                        node = node_sys;
 188                else if (!ret_vdso)
 189                        node = node_vdso;
 190                else if (!ret_vsys)
 191                        node = node_vsys;
 192
 193                bool ok = true;
 194                if (!ret_sys && (cpu_sys != cpu || node_sys != node))
 195                        ok = false;
 196                if (!ret_vdso && (cpu_vdso != cpu || node_vdso != node))
 197                        ok = false;
 198                if (!ret_vsys && (cpu_vsys != cpu || node_vsys != node))
 199                        ok = false;
 200
 201                printf("[%s]\tCPU %u:", ok ? "OK" : "FAIL", cpu);
 202                if (!ret_sys)
 203                        printf(" syscall: cpu %u, node %u", cpu_sys, node_sys);
 204                if (!ret_vdso)
 205                        printf(" vdso: cpu %u, node %u", cpu_vdso, node_vdso);
 206                if (!ret_vsys)
 207                        printf(" vsyscall: cpu %u, node %u", cpu_vsys,
 208                               node_vsys);
 209                printf("\n");
 210
 211                if (!ok)
 212                        nerrs++;
 213        }
 214}
 215
 216static bool ts_leq(const struct timespec *a, const struct timespec *b)
 217{
 218        if (a->tv_sec != b->tv_sec)
 219                return a->tv_sec < b->tv_sec;
 220        else
 221                return a->tv_nsec <= b->tv_nsec;
 222}
 223
 224static bool ts64_leq(const struct __kernel_timespec *a,
 225                     const struct __kernel_timespec *b)
 226{
 227        if (a->tv_sec != b->tv_sec)
 228                return a->tv_sec < b->tv_sec;
 229        else
 230                return a->tv_nsec <= b->tv_nsec;
 231}
 232
 233static bool tv_leq(const struct timeval *a, const struct timeval *b)
 234{
 235        if (a->tv_sec != b->tv_sec)
 236                return a->tv_sec < b->tv_sec;
 237        else
 238                return a->tv_usec <= b->tv_usec;
 239}
 240
 241static char const * const clocknames[] = {
 242        [0] = "CLOCK_REALTIME",
 243        [1] = "CLOCK_MONOTONIC",
 244        [2] = "CLOCK_PROCESS_CPUTIME_ID",
 245        [3] = "CLOCK_THREAD_CPUTIME_ID",
 246        [4] = "CLOCK_MONOTONIC_RAW",
 247        [5] = "CLOCK_REALTIME_COARSE",
 248        [6] = "CLOCK_MONOTONIC_COARSE",
 249        [7] = "CLOCK_BOOTTIME",
 250        [8] = "CLOCK_REALTIME_ALARM",
 251        [9] = "CLOCK_BOOTTIME_ALARM",
 252        [10] = "CLOCK_SGI_CYCLE",
 253        [11] = "CLOCK_TAI",
 254};
 255
 256static void test_one_clock_gettime(int clock, const char *name)
 257{
 258        struct timespec start, vdso, end;
 259        int vdso_ret, end_ret;
 260
 261        printf("[RUN]\tTesting clock_gettime for clock %s (%d)...\n", name, clock);
 262
 263        if (sys_clock_gettime(clock, &start) < 0) {
 264                if (errno == EINVAL) {
 265                        vdso_ret = vdso_clock_gettime(clock, &vdso);
 266                        if (vdso_ret == -EINVAL) {
 267                                printf("[OK]\tNo such clock.\n");
 268                        } else {
 269                                printf("[FAIL]\tNo such clock, but __vdso_clock_gettime returned %d\n", vdso_ret);
 270                                nerrs++;
 271                        }
 272                } else {
 273                        printf("[WARN]\t clock_gettime(%d) syscall returned error %d\n", clock, errno);
 274                }
 275                return;
 276        }
 277
 278        vdso_ret = vdso_clock_gettime(clock, &vdso);
 279        end_ret = sys_clock_gettime(clock, &end);
 280
 281        if (vdso_ret != 0 || end_ret != 0) {
 282                printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
 283                       vdso_ret, errno);
 284                nerrs++;
 285                return;
 286        }
 287
 288        printf("\t%llu.%09ld %llu.%09ld %llu.%09ld\n",
 289               (unsigned long long)start.tv_sec, start.tv_nsec,
 290               (unsigned long long)vdso.tv_sec, vdso.tv_nsec,
 291               (unsigned long long)end.tv_sec, end.tv_nsec);
 292
 293        if (!ts_leq(&start, &vdso) || !ts_leq(&vdso, &end)) {
 294                printf("[FAIL]\tTimes are out of sequence\n");
 295                nerrs++;
 296                return;
 297        }
 298
 299        printf("[OK]\tTest Passed.\n");
 300}
 301
 302static void test_clock_gettime(void)
 303{
 304        if (!vdso_clock_gettime) {
 305                printf("[SKIP]\tNo vDSO, so skipping clock_gettime() tests\n");
 306                return;
 307        }
 308
 309        for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]);
 310             clock++) {
 311                test_one_clock_gettime(clock, clocknames[clock]);
 312        }
 313
 314        /* Also test some invalid clock ids */
 315        test_one_clock_gettime(-1, "invalid");
 316        test_one_clock_gettime(INT_MIN, "invalid");
 317        test_one_clock_gettime(INT_MAX, "invalid");
 318}
 319
 320static void test_one_clock_gettime64(int clock, const char *name)
 321{
 322        struct __kernel_timespec start, vdso, end;
 323        int vdso_ret, end_ret;
 324
 325        printf("[RUN]\tTesting clock_gettime64 for clock %s (%d)...\n", name, clock);
 326
 327        if (sys_clock_gettime64(clock, &start) < 0) {
 328                if (errno == EINVAL) {
 329                        vdso_ret = vdso_clock_gettime64(clock, &vdso);
 330                        if (vdso_ret == -EINVAL) {
 331                                printf("[OK]\tNo such clock.\n");
 332                        } else {
 333                                printf("[FAIL]\tNo such clock, but __vdso_clock_gettime64 returned %d\n", vdso_ret);
 334                                nerrs++;
 335                        }
 336                } else {
 337                        printf("[WARN]\t clock_gettime64(%d) syscall returned error %d\n", clock, errno);
 338                }
 339                return;
 340        }
 341
 342        vdso_ret = vdso_clock_gettime64(clock, &vdso);
 343        end_ret = sys_clock_gettime64(clock, &end);
 344
 345        if (vdso_ret != 0 || end_ret != 0) {
 346                printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
 347                       vdso_ret, errno);
 348                nerrs++;
 349                return;
 350        }
 351
 352        printf("\t%llu.%09lld %llu.%09lld %llu.%09lld\n",
 353               (unsigned long long)start.tv_sec, start.tv_nsec,
 354               (unsigned long long)vdso.tv_sec, vdso.tv_nsec,
 355               (unsigned long long)end.tv_sec, end.tv_nsec);
 356
 357        if (!ts64_leq(&start, &vdso) || !ts64_leq(&vdso, &end)) {
 358                printf("[FAIL]\tTimes are out of sequence\n");
 359                nerrs++;
 360                return;
 361        }
 362
 363        printf("[OK]\tTest Passed.\n");
 364}
 365
 366static void test_clock_gettime64(void)
 367{
 368        if (!vdso_clock_gettime64) {
 369                printf("[SKIP]\tNo vDSO, so skipping clock_gettime64() tests\n");
 370                return;
 371        }
 372
 373        for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]);
 374             clock++) {
 375                test_one_clock_gettime64(clock, clocknames[clock]);
 376        }
 377
 378        /* Also test some invalid clock ids */
 379        test_one_clock_gettime64(-1, "invalid");
 380        test_one_clock_gettime64(INT_MIN, "invalid");
 381        test_one_clock_gettime64(INT_MAX, "invalid");
 382}
 383
 384static void test_gettimeofday(void)
 385{
 386        struct timeval start, vdso, end;
 387        struct timezone sys_tz, vdso_tz;
 388        int vdso_ret, end_ret;
 389
 390        if (!vdso_gettimeofday)
 391                return;
 392
 393        printf("[RUN]\tTesting gettimeofday...\n");
 394
 395        if (sys_gettimeofday(&start, &sys_tz) < 0) {
 396                printf("[FAIL]\tsys_gettimeofday failed (%d)\n", errno);
 397                nerrs++;
 398                return;
 399        }
 400
 401        vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz);
 402        end_ret = sys_gettimeofday(&end, NULL);
 403
 404        if (vdso_ret != 0 || end_ret != 0) {
 405                printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
 406                       vdso_ret, errno);
 407                nerrs++;
 408                return;
 409        }
 410
 411        printf("\t%llu.%06ld %llu.%06ld %llu.%06ld\n",
 412               (unsigned long long)start.tv_sec, start.tv_usec,
 413               (unsigned long long)vdso.tv_sec, vdso.tv_usec,
 414               (unsigned long long)end.tv_sec, end.tv_usec);
 415
 416        if (!tv_leq(&start, &vdso) || !tv_leq(&vdso, &end)) {
 417                printf("[FAIL]\tTimes are out of sequence\n");
 418                nerrs++;
 419        }
 420
 421        if (sys_tz.tz_minuteswest == vdso_tz.tz_minuteswest &&
 422            sys_tz.tz_dsttime == vdso_tz.tz_dsttime) {
 423                printf("[OK]\ttimezones match: minuteswest=%d, dsttime=%d\n",
 424                       sys_tz.tz_minuteswest, sys_tz.tz_dsttime);
 425        } else {
 426                printf("[FAIL]\ttimezones do not match\n");
 427                nerrs++;
 428        }
 429
 430        /* And make sure that passing NULL for tz doesn't crash. */
 431        vdso_gettimeofday(&vdso, NULL);
 432}
 433
 434int main(int argc, char **argv)
 435{
 436        name = (const char **)&names[VDSO_NAMES];
 437
 438        fill_function_pointers();
 439
 440        test_clock_gettime();
 441        test_clock_gettime64();
 442        test_gettimeofday();
 443
 444        /*
 445         * Test getcpu() last so that, if something goes wrong setting affinity,
 446         * we still run the other tests.
 447         */
 448        test_getcpu();
 449
 450        return nerrs ? 1 : 0;
 451}
 452