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