diff options
Diffstat (limited to 'tools/testing/selftests/x86')
-rw-r--r-- | tools/testing/selftests/x86/Makefile | 23 | ||||
-rw-r--r-- | tools/testing/selftests/x86/fsgsbase.c | 2 | ||||
-rw-r--r-- | tools/testing/selftests/x86/ioperm.c | 170 | ||||
-rw-r--r-- | tools/testing/selftests/x86/ldt_gdt.c | 16 | ||||
-rw-r--r-- | tools/testing/selftests/x86/protection_keys.c | 21 | ||||
-rw-r--r-- | tools/testing/selftests/x86/ptrace_syscall.c | 3 | ||||
-rw-r--r-- | tools/testing/selftests/x86/single_step_syscall.c | 5 | ||||
-rw-r--r-- | tools/testing/selftests/x86/sysret_rip.c | 195 | ||||
-rw-r--r-- | tools/testing/selftests/x86/test_vdso.c | 123 |
9 files changed, 522 insertions, 36 deletions
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index a89f80a5b711..38e0a9ca5d71 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -5,18 +5,21 @@ include ../lib.mk .PHONY: all all_32 all_64 warn_32bit_failure clean TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \ - check_initial_reg_state sigreturn ldt_gdt iopl \ - protection_keys + check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \ + protection_keys test_vdso TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer -TARGETS_C_64BIT_ONLY := fsgsbase +TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) TARGETS_C_64BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_64BIT_ONLY) BINARIES_32 := $(TARGETS_C_32BIT_ALL:%=%_32) BINARIES_64 := $(TARGETS_C_64BIT_ALL:%=%_64) +BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32)) +BINARIES_64 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_64)) + CFLAGS := -O2 -g -std=gnu99 -pthread -Wall UNAME_M := $(shell uname -m) @@ -40,10 +43,10 @@ all_64: $(BINARIES_64) clean: $(RM) $(BINARIES_32) $(BINARIES_64) -$(TARGETS_C_32BIT_ALL:%=%_32): %_32: %.c +$(BINARIES_32): $(OUTPUT)/%_32: %.c $(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl -lm -$(TARGETS_C_64BIT_ALL:%=%_64): %_64: %.c +$(BINARIES_64): $(OUTPUT)/%_64: %.c $(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl # x86_64 users should be encouraged to install 32-bit libraries @@ -65,12 +68,12 @@ warn_32bit_failure: endif # Some tests have additional dependencies. -sysret_ss_attrs_64: thunks.S -ptrace_syscall_32: raw_syscall_helper_32.S -test_syscall_vdso_32: thunks_32.S +$(OUTPUT)/sysret_ss_attrs_64: thunks.S +$(OUTPUT)/ptrace_syscall_32: raw_syscall_helper_32.S +$(OUTPUT)/test_syscall_vdso_32: thunks_32.S # check_initial_reg_state is special: it needs a custom entry, and it # needs to be static so that its interpreter doesn't destroy its initial # state. -check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static -check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static +$(OUTPUT)/check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static +$(OUTPUT)/check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static diff --git a/tools/testing/selftests/x86/fsgsbase.c b/tools/testing/selftests/x86/fsgsbase.c index 5b2b4b3c634c..b4967d875236 100644 --- a/tools/testing/selftests/x86/fsgsbase.c +++ b/tools/testing/selftests/x86/fsgsbase.c @@ -245,7 +245,7 @@ void do_unexpected_base(void) long ret; asm volatile ("int $0x80" : "=a" (ret) : "a" (243), "b" (low_desc) - : "flags"); + : "r8", "r9", "r10", "r11"); memcpy(&desc, low_desc, sizeof(desc)); munmap(low_desc, sizeof(desc)); diff --git a/tools/testing/selftests/x86/ioperm.c b/tools/testing/selftests/x86/ioperm.c new file mode 100644 index 000000000000..b77313ba2ab1 --- /dev/null +++ b/tools/testing/selftests/x86/ioperm.c @@ -0,0 +1,170 @@ +/* + * ioperm.c - Test case for ioperm(2) + * Copyright (c) 2015 Andrew Lutomirski + */ + +#define _GNU_SOURCE +#include <err.h> +#include <stdio.h> +#include <stdint.h> +#include <signal.h> +#include <setjmp.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdbool.h> +#include <sched.h> +#include <sys/io.h> + +static int nerrs = 0; + +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), + int flags) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handler; + sa.sa_flags = SA_SIGINFO | flags; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); + +} + +static void clearhandler(int sig) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +static jmp_buf jmpbuf; + +static void sigsegv(int sig, siginfo_t *si, void *ctx_void) +{ + siglongjmp(jmpbuf, 1); +} + +static bool try_outb(unsigned short port) +{ + sethandler(SIGSEGV, sigsegv, SA_RESETHAND); + if (sigsetjmp(jmpbuf, 1) != 0) { + return false; + } else { + asm volatile ("outb %%al, %w[port]" + : : [port] "Nd" (port), "a" (0)); + return true; + } + clearhandler(SIGSEGV); +} + +static void expect_ok(unsigned short port) +{ + if (!try_outb(port)) { + printf("[FAIL]\toutb to 0x%02hx failed\n", port); + exit(1); + } + + printf("[OK]\toutb to 0x%02hx worked\n", port); +} + +static void expect_gp(unsigned short port) +{ + if (try_outb(port)) { + printf("[FAIL]\toutb to 0x%02hx worked\n", port); + exit(1); + } + + printf("[OK]\toutb to 0x%02hx failed\n", port); +} + +int main(void) +{ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) + err(1, "sched_setaffinity to CPU 0"); + + expect_gp(0x80); + expect_gp(0xed); + + /* + * Probe for ioperm support. Note that clearing ioperm bits + * works even as nonroot. + */ + printf("[RUN]\tenable 0x80\n"); + if (ioperm(0x80, 1, 1) != 0) { + printf("[OK]\tioperm(0x80, 1, 1) failed (%d) -- try running as root\n", + errno); + return 0; + } + expect_ok(0x80); + expect_gp(0xed); + + printf("[RUN]\tdisable 0x80\n"); + if (ioperm(0x80, 1, 0) != 0) { + printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno); + return 1; + } + expect_gp(0x80); + expect_gp(0xed); + + /* Make sure that fork() preserves ioperm. */ + if (ioperm(0x80, 1, 1) != 0) { + printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno); + return 1; + } + + pid_t child = fork(); + if (child == -1) + err(1, "fork"); + + if (child == 0) { + printf("[RUN]\tchild: check that we inherited permissions\n"); + expect_ok(0x80); + expect_gp(0xed); + return 0; + } else { + int status; + if (waitpid(child, &status, 0) != child || + !WIFEXITED(status)) { + printf("[FAIL]\tChild died\n"); + nerrs++; + } else if (WEXITSTATUS(status) != 0) { + printf("[FAIL]\tChild failed\n"); + nerrs++; + } else { + printf("[OK]\tChild succeeded\n"); + } + } + + /* Test the capability checks. */ + + printf("\tDrop privileges\n"); + if (setresuid(1, 1, 1) != 0) { + printf("[WARN]\tDropping privileges failed\n"); + return 0; + } + + printf("[RUN]\tdisable 0x80\n"); + if (ioperm(0x80, 1, 0) != 0) { + printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno); + return 1; + } + printf("[OK]\tit worked\n"); + + printf("[RUN]\tenable 0x80 again\n"); + if (ioperm(0x80, 1, 1) == 0) { + printf("[FAIL]\tit succeeded but should have failed.\n"); + return 1; + } + printf("[OK]\tit failed\n"); + return 0; +} diff --git a/tools/testing/selftests/x86/ldt_gdt.c b/tools/testing/selftests/x86/ldt_gdt.c index 4af47079cf04..f6121612e769 100644 --- a/tools/testing/selftests/x86/ldt_gdt.c +++ b/tools/testing/selftests/x86/ldt_gdt.c @@ -45,6 +45,12 @@ #define AR_DB (1 << 22) #define AR_G (1 << 23) +#ifdef __x86_64__ +# define INT80_CLOBBERS "r8", "r9", "r10", "r11" +#else +# define INT80_CLOBBERS +#endif + static int nerrs; /* Points to an array of 1024 ints, each holding its own index. */ @@ -588,7 +594,7 @@ static int invoke_set_thread_area(void) asm volatile ("int $0x80" : "=a" (ret), "+m" (low_user_desc) : "a" (243), "b" (low_user_desc) - : "flags"); + : INT80_CLOBBERS); return ret; } @@ -657,7 +663,7 @@ static void test_gdt_invalidation(void) "+a" (eax) : "m" (low_user_desc_clear), [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear) - : "flags"); + : INT80_CLOBBERS); if (sel != 0) { result = "FAIL"; @@ -688,7 +694,7 @@ static void test_gdt_invalidation(void) "+a" (eax) : "m" (low_user_desc_clear), [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear) - : "flags"); + : INT80_CLOBBERS); if (sel != 0) { result = "FAIL"; @@ -721,7 +727,7 @@ static void test_gdt_invalidation(void) "+a" (eax) : "m" (low_user_desc_clear), [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear) - : "flags"); + : INT80_CLOBBERS); #ifdef __x86_64__ syscall(SYS_arch_prctl, ARCH_GET_FS, &new_base); @@ -774,7 +780,7 @@ static void test_gdt_invalidation(void) "+a" (eax) : "m" (low_user_desc_clear), [arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear) - : "flags"); + : INT80_CLOBBERS); #ifdef __x86_64__ syscall(SYS_arch_prctl, ARCH_GET_GS, &new_base); diff --git a/tools/testing/selftests/x86/protection_keys.c b/tools/testing/selftests/x86/protection_keys.c index bdd58c78902e..3237bc010e1c 100644 --- a/tools/testing/selftests/x86/protection_keys.c +++ b/tools/testing/selftests/x86/protection_keys.c @@ -192,7 +192,7 @@ void lots_o_noops_around_write(int *write_to_me) #define SYS_pkey_alloc 381 #define SYS_pkey_free 382 #define REG_IP_IDX REG_EIP -#define si_pkey_offset 0x18 +#define si_pkey_offset 0x14 #else #define SYS_mprotect_key 329 #define SYS_pkey_alloc 330 @@ -462,7 +462,7 @@ void pkey_disable_set(int pkey, int flags) unsigned long syscall_flags = 0; int ret; int pkey_rights; - u32 orig_pkru; + u32 orig_pkru = rdpkru(); dprintf1("START->%s(%d, 0x%x)\n", __func__, pkey, flags); @@ -812,8 +812,6 @@ void setup_hugetlbfs(void) { int err; int fd; - int validated_nr_pages; - int i; char buf[] = "123"; if (geteuid() != 0) { @@ -1116,11 +1114,6 @@ void test_pkey_syscalls_on_non_allocated_pkey(int *ptr, u16 pkey) err = sys_pkey_free(i); pkey_assert(err); - /* not enforced when pkey_get() is not a syscall - err = pkey_get(i, 0); - pkey_assert(err < 0); - */ - err = sys_pkey_free(i); pkey_assert(err); @@ -1133,14 +1126,8 @@ void test_pkey_syscalls_on_non_allocated_pkey(int *ptr, u16 pkey) void test_pkey_syscalls_bad_args(int *ptr, u16 pkey) { int err; - int bad_flag = (PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) + 1; int bad_pkey = NR_PKEYS+99; - /* not enforced when pkey_get() is not a syscall - err = pkey_get(bad_pkey, bad_flag); - pkey_assert(err < 0); - */ - /* pass a known-invalid pkey in: */ err = sys_mprotect_pkey(ptr, PAGE_SIZE, PROT_READ, bad_pkey); pkey_assert(err); @@ -1149,8 +1136,6 @@ void test_pkey_syscalls_bad_args(int *ptr, u16 pkey) /* Assumes that all pkeys other than 'pkey' are unallocated */ void test_pkey_alloc_exhaust(int *ptr, u16 pkey) { - unsigned long flags; - unsigned long init_val; int err; int allocated_pkeys[NR_PKEYS] = {0}; int nr_allocated_pkeys = 0; @@ -1367,7 +1352,7 @@ void run_tests_once(void) tracing_off(); close_test_fds(); - printf("test %2d PASSED (itertation %d)\n", test_nr, iteration_nr); + printf("test %2d PASSED (iteration %d)\n", test_nr, iteration_nr); dprintf1("======================\n\n"); } iteration_nr++; diff --git a/tools/testing/selftests/x86/ptrace_syscall.c b/tools/testing/selftests/x86/ptrace_syscall.c index b037ce9cf116..eaea92439708 100644 --- a/tools/testing/selftests/x86/ptrace_syscall.c +++ b/tools/testing/selftests/x86/ptrace_syscall.c @@ -58,7 +58,8 @@ static void do_full_int80(struct syscall_args32 *args) asm volatile ("int $0x80" : "+a" (args->nr), "+b" (args->arg0), "+c" (args->arg1), "+d" (args->arg2), - "+S" (args->arg3), "+D" (args->arg4), "+r" (bp)); + "+S" (args->arg3), "+D" (args->arg4), "+r" (bp) + : : "r8", "r9", "r10", "r11"); args->arg5 = bp; #else sys32_helper(args, int80_and_ret); diff --git a/tools/testing/selftests/x86/single_step_syscall.c b/tools/testing/selftests/x86/single_step_syscall.c index 50c26358e8b7..a48da95c18fd 100644 --- a/tools/testing/selftests/x86/single_step_syscall.c +++ b/tools/testing/selftests/x86/single_step_syscall.c @@ -56,9 +56,11 @@ static volatile sig_atomic_t sig_traps; #ifdef __x86_64__ # define REG_IP REG_RIP # define WIDTH "q" +# define INT80_CLOBBERS "r8", "r9", "r10", "r11" #else # define REG_IP REG_EIP # define WIDTH "l" +# define INT80_CLOBBERS #endif static unsigned long get_eflags(void) @@ -140,7 +142,8 @@ int main() printf("[RUN]\tSet TF and check int80\n"); set_eflags(get_eflags() | X86_EFLAGS_TF); - asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid)); + asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid) + : INT80_CLOBBERS); check_result(); /* diff --git a/tools/testing/selftests/x86/sysret_rip.c b/tools/testing/selftests/x86/sysret_rip.c new file mode 100644 index 000000000000..d85ec5b3671c --- /dev/null +++ b/tools/testing/selftests/x86/sysret_rip.c @@ -0,0 +1,195 @@ +/* + * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls + * Copyright (c) 2014-2016 Andrew Lutomirski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <sys/syscall.h> +#include <err.h> +#include <stddef.h> +#include <stdbool.h> +#include <setjmp.h> +#include <sys/user.h> +#include <sys/mman.h> +#include <assert.h> + + +asm ( + ".pushsection \".text\", \"ax\"\n\t" + ".balign 4096\n\t" + "test_page: .globl test_page\n\t" + ".fill 4094,1,0xcc\n\t" + "test_syscall_insn:\n\t" + "syscall\n\t" + ".ifne . - test_page - 4096\n\t" + ".error \"test page is not one page long\"\n\t" + ".endif\n\t" + ".popsection" + ); + +extern const char test_page[]; +static void const *current_test_page_addr = test_page; + +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), + int flags) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handler; + sa.sa_flags = SA_SIGINFO | flags; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +static void clearhandler(int sig) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +/* State used by our signal handlers. */ +static gregset_t initial_regs; + +static volatile unsigned long rip; + +static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { + printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n", + rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); + fflush(stdout); + _exit(1); + } + + memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); + + printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip); +} + +static void sigusr1(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); + + /* Set IP and CX to match so that SYSRET can happen. */ + ctx->uc_mcontext.gregs[REG_RIP] = rip; + ctx->uc_mcontext.gregs[REG_RCX] = rip; + + /* R11 and EFLAGS should already match. */ + assert(ctx->uc_mcontext.gregs[REG_EFL] == + ctx->uc_mcontext.gregs[REG_R11]); + + sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND); + + return; +} + +static void test_sigreturn_to(unsigned long ip) +{ + rip = ip; + printf("[RUN]\tsigreturn to 0x%lx\n", ip); + raise(SIGUSR1); +} + +static jmp_buf jmpbuf; + +static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void) +{ + ucontext_t *ctx = (ucontext_t*)ctx_void; + + if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { + printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n", + rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); + fflush(stdout); + _exit(1); + } + + siglongjmp(jmpbuf, 1); +} + +static void test_syscall_fallthrough_to(unsigned long ip) +{ + void *new_address = (void *)(ip - 4096); + void *ret; + + printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip); + + ret = mremap((void *)current_test_page_addr, 4096, 4096, + MREMAP_MAYMOVE | MREMAP_FIXED, new_address); + if (ret == MAP_FAILED) { + if (ip <= (1UL << 47) - PAGE_SIZE) { + err(1, "mremap to %p", new_address); + } else { + printf("[OK]\tmremap to %p failed\n", new_address); + return; + } + } + + if (ret != new_address) + errx(1, "mremap malfunctioned: asked for %p but got %p\n", + new_address, ret); + + current_test_page_addr = new_address; + rip = ip; + + if (sigsetjmp(jmpbuf, 1) == 0) { + asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid), + [syscall_insn] "rm" (ip - 2)); + errx(1, "[FAIL]\tSyscall trampoline returned"); + } + + printf("[OK]\tWe survived\n"); +} + +int main() +{ + /* + * When the kernel returns from a slow-path syscall, it will + * detect whether SYSRET is appropriate. If it incorrectly + * thinks that SYSRET is appropriate when RIP is noncanonical, + * it'll crash on Intel CPUs. + */ + sethandler(SIGUSR1, sigusr1, 0); + for (int i = 47; i < 64; i++) + test_sigreturn_to(1UL<<i); + + clearhandler(SIGUSR1); + + sethandler(SIGSEGV, sigsegv_for_fallthrough, 0); + + /* One extra test to check that we didn't screw up the mremap logic. */ + test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE); + + /* These are the interesting cases. */ + for (int i = 47; i < 64; i++) { + test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE); + test_syscall_fallthrough_to(1UL<<i); + } + + return 0; +} diff --git a/tools/testing/selftests/x86/test_vdso.c b/tools/testing/selftests/x86/test_vdso.c new file mode 100644 index 000000000000..65d7a2bf7e14 --- /dev/null +++ b/tools/testing/selftests/x86/test_vdso.c @@ -0,0 +1,123 @@ +/* + * ldt_gdt.c - Test cases for LDT and GDT access + * Copyright (c) 2011-2015 Andrew Lutomirski + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <sys/time.h> +#include <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <dlfcn.h> +#include <string.h> +#include <errno.h> +#include <sched.h> +#include <stdbool.h> + +#ifndef SYS_getcpu +# ifdef __x86_64__ +# define SYS_getcpu 309 +# else +# define SYS_getcpu 318 +# endif +#endif + +int nerrs = 0; + +#ifdef __x86_64__ +# define VSYS(x) (x) +#else +# define VSYS(x) 0 +#endif + +typedef long (*getcpu_t)(unsigned *, unsigned *, void *); + +const getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800); +getcpu_t vdso_getcpu; + +void fill_function_pointers() +{ + 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("[WARN]\tfailed to find vDSO\n"); + return; + } + + vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu"); + if (!vdso_getcpu) + printf("Warning: failed to find getcpu in vDSO\n"); +} + +static long sys_getcpu(unsigned * cpu, unsigned * node, + void* cache) +{ + return syscall(__NR_getcpu, cpu, node, cache); +} + +static void test_getcpu(void) +{ + printf("[RUN]\tTesting getcpu...\n"); + + for (int cpu = 0; ; cpu++) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) + return; + + unsigned cpu_sys, cpu_vdso, cpu_vsys, + node_sys, node_vdso, node_vsys; + long ret_sys, ret_vdso = 1, ret_vsys = 1; + unsigned node; + + 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) + node = node_sys; + else if (!ret_vdso) + node = node_vdso; + else if (!ret_vsys) + node = node_vsys; + + bool ok = true; + if (!ret_sys && (cpu_sys != cpu || node_sys != node)) + ok = false; + if (!ret_vdso && (cpu_vdso != cpu || node_vdso != node)) + ok = false; + if (!ret_vsys && (cpu_vsys != cpu || node_vsys != node)) + ok = false; + + printf("[%s]\tCPU %u:", ok ? "OK" : "FAIL", cpu); + if (!ret_sys) + printf(" syscall: cpu %u, node %u", cpu_sys, node_sys); + if (!ret_vdso) + printf(" vdso: cpu %u, node %u", cpu_vdso, node_vdso); + if (!ret_vsys) + printf(" vsyscall: cpu %u, node %u", cpu_vsys, + node_vsys); + printf("\n"); + + if (!ok) + nerrs++; + } +} + +int main(int argc, char **argv) +{ + fill_function_pointers(); + + test_getcpu(); + + return nerrs ? 1 : 0; +} |