sud_benchmark.c (4757B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (c) 2020 Collabora Ltd. 4 * 5 * Benchmark and test syscall user dispatch 6 */ 7 8#define _GNU_SOURCE 9#include <stdio.h> 10#include <string.h> 11#include <stdlib.h> 12#include <signal.h> 13#include <errno.h> 14#include <time.h> 15#include <sys/time.h> 16#include <unistd.h> 17#include <sys/sysinfo.h> 18#include <sys/prctl.h> 19#include <sys/syscall.h> 20 21#ifndef PR_SET_SYSCALL_USER_DISPATCH 22# define PR_SET_SYSCALL_USER_DISPATCH 59 23# define PR_SYS_DISPATCH_OFF 0 24# define PR_SYS_DISPATCH_ON 1 25# define SYSCALL_DISPATCH_FILTER_ALLOW 0 26# define SYSCALL_DISPATCH_FILTER_BLOCK 1 27#endif 28 29#ifdef __NR_syscalls 30# define MAGIC_SYSCALL_1 (__NR_syscalls + 1) /* Bad Linux syscall number */ 31#else 32# define MAGIC_SYSCALL_1 (0xff00) /* Bad Linux syscall number */ 33#endif 34 35/* 36 * To test returning from a sigsys with selector blocked, the test 37 * requires some per-architecture support (i.e. knowledge about the 38 * signal trampoline address). On i386, we know it is on the vdso, and 39 * a small trampoline is open-coded for x86_64. Other architectures 40 * that have a trampoline in the vdso will support TEST_BLOCKED_RETURN 41 * out of the box, but don't enable them until they support syscall user 42 * dispatch. 43 */ 44#if defined(__x86_64__) || defined(__i386__) 45#define TEST_BLOCKED_RETURN 46#endif 47 48#ifdef __x86_64__ 49void* (syscall_dispatcher_start)(void); 50void* (syscall_dispatcher_end)(void); 51#else 52unsigned long syscall_dispatcher_start = 0; 53unsigned long syscall_dispatcher_end = 0; 54#endif 55 56unsigned long trapped_call_count = 0; 57unsigned long native_call_count = 0; 58 59char selector; 60#define SYSCALL_BLOCK (selector = SYSCALL_DISPATCH_FILTER_BLOCK) 61#define SYSCALL_UNBLOCK (selector = SYSCALL_DISPATCH_FILTER_ALLOW) 62 63#define CALIBRATION_STEP 100000 64#define CALIBRATE_TO_SECS 5 65int factor; 66 67static double one_sysinfo_step(void) 68{ 69 struct timespec t1, t2; 70 int i; 71 struct sysinfo info; 72 73 clock_gettime(CLOCK_MONOTONIC, &t1); 74 for (i = 0; i < CALIBRATION_STEP; i++) 75 sysinfo(&info); 76 clock_gettime(CLOCK_MONOTONIC, &t2); 77 return (t2.tv_sec - t1.tv_sec) + 1.0e-9 * (t2.tv_nsec - t1.tv_nsec); 78} 79 80static void calibrate_set(void) 81{ 82 double elapsed = 0; 83 84 printf("Calibrating test set to last ~%d seconds...\n", CALIBRATE_TO_SECS); 85 86 while (elapsed < 1) { 87 elapsed += one_sysinfo_step(); 88 factor += CALIBRATE_TO_SECS; 89 } 90 91 printf("test iterations = %d\n", CALIBRATION_STEP * factor); 92} 93 94static double perf_syscall(void) 95{ 96 unsigned int i; 97 double partial = 0; 98 99 for (i = 0; i < factor; ++i) 100 partial += one_sysinfo_step()/(CALIBRATION_STEP*factor); 101 return partial; 102} 103 104static void handle_sigsys(int sig, siginfo_t *info, void *ucontext) 105{ 106 char buf[1024]; 107 int len; 108 109 SYSCALL_UNBLOCK; 110 111 /* printf and friends are not signal-safe. */ 112 len = snprintf(buf, 1024, "Caught sys_%x\n", info->si_syscall); 113 write(1, buf, len); 114 115 if (info->si_syscall == MAGIC_SYSCALL_1) 116 trapped_call_count++; 117 else 118 native_call_count++; 119 120#ifdef TEST_BLOCKED_RETURN 121 SYSCALL_BLOCK; 122#endif 123 124#ifdef __x86_64__ 125 __asm__ volatile("movq $0xf, %rax"); 126 __asm__ volatile("leaveq"); 127 __asm__ volatile("add $0x8, %rsp"); 128 __asm__ volatile("syscall_dispatcher_start:"); 129 __asm__ volatile("syscall"); 130 __asm__ volatile("nop"); /* Landing pad within dispatcher area */ 131 __asm__ volatile("syscall_dispatcher_end:"); 132#endif 133 134} 135 136int main(void) 137{ 138 struct sigaction act; 139 double time1, time2; 140 int ret; 141 sigset_t mask; 142 143 memset(&act, 0, sizeof(act)); 144 sigemptyset(&mask); 145 146 act.sa_sigaction = handle_sigsys; 147 act.sa_flags = SA_SIGINFO; 148 act.sa_mask = mask; 149 150 calibrate_set(); 151 152 time1 = perf_syscall(); 153 printf("Avg syscall time %.0lfns.\n", time1 * 1.0e9); 154 155 ret = sigaction(SIGSYS, &act, NULL); 156 if (ret) { 157 perror("Error sigaction:"); 158 exit(-1); 159 } 160 161 fprintf(stderr, "Enabling syscall trapping.\n"); 162 163 if (prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 164 syscall_dispatcher_start, 165 (syscall_dispatcher_end - syscall_dispatcher_start + 1), 166 &selector)) { 167 perror("prctl failed\n"); 168 exit(-1); 169 } 170 171 SYSCALL_BLOCK; 172 syscall(MAGIC_SYSCALL_1); 173 174#ifdef TEST_BLOCKED_RETURN 175 if (selector == SYSCALL_DISPATCH_FILTER_ALLOW) { 176 fprintf(stderr, "Failed to return with selector blocked.\n"); 177 exit(-1); 178 } 179#endif 180 181 SYSCALL_UNBLOCK; 182 183 if (!trapped_call_count) { 184 fprintf(stderr, "syscall trapping does not work.\n"); 185 exit(-1); 186 } 187 188 time2 = perf_syscall(); 189 190 if (native_call_count) { 191 perror("syscall trapping intercepted more syscalls than expected\n"); 192 exit(-1); 193 } 194 195 printf("trapped_call_count %lu, native_call_count %lu.\n", 196 trapped_call_count, native_call_count); 197 printf("Avg syscall time %.0lfns.\n", time2 * 1.0e9); 198 printf("Interception overhead: %.1lf%% (+%.0lfns).\n", 199 100.0 * (time2 / time1 - 1.0), 1.0e9 * (time2 - time1)); 200 return 0; 201 202}