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 if (!vdso_clock_gettime) { 263 printf("[SKIP]\tNo vDSO, so skipping clock_gettime() tests\n"); 264 return; 265 } 266 267 for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]); 268 clock++) { 269 test_one_clock_gettime(clock, clocknames[clock]); 270 } 271 272 /* Also test some invalid clock ids */ 273 test_one_clock_gettime(-1, "invalid"); 274 test_one_clock_gettime(INT_MIN, "invalid"); 275 test_one_clock_gettime(INT_MAX, "invalid"); 276} 277 278static void test_gettimeofday(void) 279{ 280 struct timeval start, vdso, end; 281 struct timezone sys_tz, vdso_tz; 282 int vdso_ret, end_ret; 283 284 if (!vdso_gettimeofday) 285 return; 286 287 printf("[RUN]\tTesting gettimeofday...\n"); 288 289 if (sys_gettimeofday(&start, &sys_tz) < 0) { 290 printf("[FAIL]\tsys_gettimeofday failed (%d)\n", errno); 291 nerrs++; 292 return; 293 } 294 295 vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz); 296 end_ret = sys_gettimeofday(&end, NULL); 297 298 if (vdso_ret != 0 || end_ret != 0) { 299 printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n", 300 vdso_ret, errno); 301 nerrs++; 302 return; 303 } 304 305 printf("\t%llu.%06ld %llu.%06ld %llu.%06ld\n", 306 (unsigned long long)start.tv_sec, start.tv_usec, 307 (unsigned long long)vdso.tv_sec, vdso.tv_usec, 308 (unsigned long long)end.tv_sec, end.tv_usec); 309 310 if (!tv_leq(&start, &vdso) || !tv_leq(&vdso, &end)) { 311 printf("[FAIL]\tTimes are out of sequence\n"); 312 nerrs++; 313 } 314 315 if (sys_tz.tz_minuteswest == vdso_tz.tz_minuteswest && 316 sys_tz.tz_dsttime == vdso_tz.tz_dsttime) { 317 printf("[OK]\ttimezones match: minuteswest=%d, dsttime=%d\n", 318 sys_tz.tz_minuteswest, sys_tz.tz_dsttime); 319 } else { 320 printf("[FAIL]\ttimezones do not match\n"); 321 nerrs++; 322 } 323 324 /* And make sure that passing NULL for tz doesn't crash. */ 325 vdso_gettimeofday(&vdso, NULL); 326} 327 328int main(int argc, char **argv) 329{ 330 fill_function_pointers(); 331 332 test_clock_gettime(); 333 test_gettimeofday(); 334 335 /* 336 * Test getcpu() last so that, if something goes wrong setting affinity, 337 * we still run the other tests. 338 */ 339 test_getcpu(); 340 341 return nerrs ? 1 : 0; 342} 343