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