203 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2020 Collabora Ltd.4 *5 * Benchmark and test syscall user dispatch6 */7 8#define _GNU_SOURCE9#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_DISPATCH22# define PR_SET_SYSCALL_USER_DISPATCH 5923# define PR_SYS_DISPATCH_OFF 024# define PR_SYS_DISPATCH_ON 125# define SYSCALL_DISPATCH_FILTER_ALLOW 026# define SYSCALL_DISPATCH_FILTER_BLOCK 127#endif28 29#ifdef __NR_syscalls30# define MAGIC_SYSCALL_1 (__NR_syscalls + 1) /* Bad Linux syscall number */31#else32# define MAGIC_SYSCALL_1 (0xff00) /* Bad Linux syscall number */33#endif34 35/*36 * To test returning from a sigsys with selector blocked, the test37 * requires some per-architecture support (i.e. knowledge about the38 * signal trampoline address). On i386, we know it is on the vdso, and39 * a small trampoline is open-coded for x86_64. Other architectures40 * that have a trampoline in the vdso will support TEST_BLOCKED_RETURN41 * out of the box, but don't enable them until they support syscall user42 * dispatch.43 */44#if defined(__x86_64__) || defined(__i386__)45#define TEST_BLOCKED_RETURN46#endif47 48#ifdef __x86_64__49void* (syscall_dispatcher_start)(void);50void* (syscall_dispatcher_end)(void);51#else52unsigned long syscall_dispatcher_start = 0;53unsigned long syscall_dispatcher_end = 0;54#endif55 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 10000064#define CALIBRATE_TO_SECS 565int 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 else118 native_call_count++;119 120#ifdef TEST_BLOCKED_RETURN121 SYSCALL_BLOCK;122#endif123 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#endif133 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_RETURN175 if (selector == SYSCALL_DISPATCH_FILTER_ALLOW) {176 fprintf(stderr, "Failed to return with selector blocked.\n");177 exit(-1);178 }179#endif180 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}203