1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * unwind_vdso.c - tests unwind info for AT_SYSINFO in the vDSO 4 * Copyright (c) 2014-2015 Andrew Lutomirski 5 * 6 * This tests __kernel_vsyscall's unwind info. 7 */ 8 9 #define _GNU_SOURCE 10 11 #include <features.h> 12 #include <stdio.h> 13 14 #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16 15 16 int main() 17 { 18 /* We need getauxval(). */ 19 printf("[SKIP]\tGLIBC before 2.16 cannot compile this test\n"); 20 return 0; 21 } 22 23 #else 24 25 #include <sys/time.h> 26 #include <stdlib.h> 27 #include <syscall.h> 28 #include <unistd.h> 29 #include <string.h> 30 #include <inttypes.h> 31 #include <sys/mman.h> 32 #include <signal.h> 33 #include <sys/ucontext.h> 34 #include <err.h> 35 #include <stddef.h> 36 #include <stdbool.h> 37 #include <sys/ptrace.h> 38 #include <sys/user.h> 39 #include <link.h> 40 #include <sys/auxv.h> 41 #include <dlfcn.h> 42 #include <unwind.h> 43 44 static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), 45 int flags) 46 { 47 struct sigaction sa; 48 memset(&sa, 0, sizeof(sa)); 49 sa.sa_sigaction = handler; 50 sa.sa_flags = SA_SIGINFO | flags; 51 sigemptyset(&sa.sa_mask); 52 if (sigaction(sig, &sa, 0)) 53 err(1, "sigaction"); 54 } 55 56 #ifdef __x86_64__ 57 # define WIDTH "q" 58 #else 59 # define WIDTH "l" 60 #endif 61 62 static unsigned long get_eflags(void) 63 { 64 unsigned long eflags; 65 asm volatile ("pushf" WIDTH "\n\tpop" WIDTH " %0" : "=rm" (eflags)); 66 return eflags; 67 } 68 69 static void set_eflags(unsigned long eflags) 70 { 71 asm volatile ("push" WIDTH " %0\n\tpopf" WIDTH 72 : : "rm" (eflags) : "flags"); 73 } 74 75 #define X86_EFLAGS_TF (1UL << 8) 76 77 static volatile sig_atomic_t nerrs; 78 static unsigned long sysinfo; 79 static bool got_sysinfo = false; 80 static unsigned long return_address; 81 82 struct unwind_state { 83 unsigned long ip; /* trap source */ 84 int depth; /* -1 until we hit the trap source */ 85 }; 86 87 _Unwind_Reason_Code trace_fn(struct _Unwind_Context * ctx, void *opaque) 88 { 89 struct unwind_state *state = opaque; 90 unsigned long ip = _Unwind_GetIP(ctx); 91 92 if (state->depth == -1) { 93 if (ip == state->ip) 94 state->depth = 0; 95 else 96 return _URC_NO_REASON; /* Not there yet */ 97 } 98 printf("\t 0x%lx\n", ip); 99 100 if (ip == return_address) { 101 /* Here we are. */ 102 unsigned long eax = _Unwind_GetGR(ctx, 0); 103 unsigned long ecx = _Unwind_GetGR(ctx, 1); 104 unsigned long edx = _Unwind_GetGR(ctx, 2); 105 unsigned long ebx = _Unwind_GetGR(ctx, 3); 106 unsigned long ebp = _Unwind_GetGR(ctx, 5); 107 unsigned long esi = _Unwind_GetGR(ctx, 6); 108 unsigned long edi = _Unwind_GetGR(ctx, 7); 109 bool ok = (eax == SYS_getpid || eax == getpid()) && 110 ebx == 1 && ecx == 2 && edx == 3 && 111 esi == 4 && edi == 5 && ebp == 6; 112 113 if (!ok) 114 nerrs++; 115 printf("[%s]\t NR = %ld, args = %ld, %ld, %ld, %ld, %ld, %ld\n", 116 (ok ? "OK" : "FAIL"), 117 eax, ebx, ecx, edx, esi, edi, ebp); 118 119 return _URC_NORMAL_STOP; 120 } else { 121 state->depth++; 122 return _URC_NO_REASON; 123 } 124 } 125 126 static void sigtrap(int sig, siginfo_t *info, void *ctx_void) 127 { 128 ucontext_t *ctx = (ucontext_t *)ctx_void; 129 struct unwind_state state; 130 unsigned long ip = ctx->uc_mcontext.gregs[REG_EIP]; 131 132 if (!got_sysinfo && ip == sysinfo) { 133 got_sysinfo = true; 134 135 /* Find the return address. */ 136 return_address = *(unsigned long *)(unsigned long)ctx->uc_mcontext.gregs[REG_ESP]; 137 138 printf("\tIn vsyscall at 0x%lx, returning to 0x%lx\n", 139 ip, return_address); 140 } 141 142 if (!got_sysinfo) 143 return; /* Not there yet */ 144 145 if (ip == return_address) { 146 ctx->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF; 147 printf("\tVsyscall is done\n"); 148 return; 149 } 150 151 printf("\tSIGTRAP at 0x%lx\n", ip); 152 153 state.ip = ip; 154 state.depth = -1; 155 _Unwind_Backtrace(trace_fn, &state); 156 } 157 158 int main() 159 { 160 sysinfo = getauxval(AT_SYSINFO); 161 printf("\tAT_SYSINFO is 0x%lx\n", sysinfo); 162 163 Dl_info info; 164 if (!dladdr((void *)sysinfo, &info)) { 165 printf("[WARN]\tdladdr failed on AT_SYSINFO\n"); 166 } else { 167 printf("[OK]\tAT_SYSINFO maps to %s, loaded at 0x%p\n", 168 info.dli_fname, info.dli_fbase); 169 } 170 171 sethandler(SIGTRAP, sigtrap, 0); 172 173 syscall(SYS_getpid); /* Force symbol binding without TF set. */ 174 printf("[RUN]\tSet TF and check a fast syscall\n"); 175 set_eflags(get_eflags() | X86_EFLAGS_TF); 176 syscall(SYS_getpid, 1, 2, 3, 4, 5, 6); 177 if (!got_sysinfo) { 178 set_eflags(get_eflags() & ~X86_EFLAGS_TF); 179 180 /* 181 * The most likely cause of this is that you're on Debian or 182 * a Debian-based distro, you're missing libc6-i686, and you're 183 * affected by libc/19006 (https://sourceware.org/PR19006). 184 */ 185 printf("[WARN]\tsyscall(2) didn't enter AT_SYSINFO\n"); 186 } 187 188 if (get_eflags() & X86_EFLAGS_TF) { 189 printf("[FAIL]\tTF is still set\n"); 190 nerrs++; 191 } 192 193 if (nerrs) { 194 printf("[FAIL]\tThere were errors\n"); 195 return 1; 196 } else { 197 printf("[OK]\tAll is well\n"); 198 return 0; 199 } 200 } 201 202 #endif /* New enough libc */ 203