diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/testing/selftests/Makefile | 11 | ||||
-rw-r--r-- | tools/testing/selftests/breakpoints/Makefile | 20 | ||||
-rw-r--r-- | tools/testing/selftests/breakpoints/breakpoint_test.c | 394 | ||||
-rw-r--r-- | tools/testing/selftests/run_tests | 8 |
4 files changed, 433 insertions, 0 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile new file mode 100644 index 000000000000..4ec84018cc13 --- /dev/null +++ b/tools/testing/selftests/Makefile | |||
@@ -0,0 +1,11 @@ | |||
1 | TARGETS = breakpoints | ||
2 | |||
3 | all: | ||
4 | for TARGET in $(TARGETS); do \ | ||
5 | make -C $$TARGET; \ | ||
6 | done; | ||
7 | |||
8 | clean: | ||
9 | for TARGET in $(TARGETS); do \ | ||
10 | make -C $$TARGET clean; \ | ||
11 | done; | ||
diff --git a/tools/testing/selftests/breakpoints/Makefile b/tools/testing/selftests/breakpoints/Makefile new file mode 100644 index 000000000000..f362722cdce7 --- /dev/null +++ b/tools/testing/selftests/breakpoints/Makefile | |||
@@ -0,0 +1,20 @@ | |||
1 | # Taken from perf makefile | ||
2 | uname_M := $(shell uname -m 2>/dev/null || echo not) | ||
3 | ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/) | ||
4 | ifeq ($(ARCH),i386) | ||
5 | ARCH := x86 | ||
6 | endif | ||
7 | ifeq ($(ARCH),x86_64) | ||
8 | ARCH := x86 | ||
9 | endif | ||
10 | |||
11 | |||
12 | all: | ||
13 | ifeq ($(ARCH),x86) | ||
14 | gcc breakpoint_test.c -o run_test | ||
15 | else | ||
16 | echo "Not an x86 target, can't build breakpoints selftests" | ||
17 | endif | ||
18 | |||
19 | clean: | ||
20 | rm -fr run_test | ||
diff --git a/tools/testing/selftests/breakpoints/breakpoint_test.c b/tools/testing/selftests/breakpoints/breakpoint_test.c new file mode 100644 index 000000000000..a0743f3b2b57 --- /dev/null +++ b/tools/testing/selftests/breakpoints/breakpoint_test.c | |||
@@ -0,0 +1,394 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com> | ||
3 | * | ||
4 | * Licensed under the terms of the GNU GPL License version 2 | ||
5 | * | ||
6 | * Selftests for breakpoints (and more generally the do_debug() path) in x86. | ||
7 | */ | ||
8 | |||
9 | |||
10 | #include <sys/ptrace.h> | ||
11 | #include <unistd.h> | ||
12 | #include <stddef.h> | ||
13 | #include <sys/user.h> | ||
14 | #include <stdio.h> | ||
15 | #include <stdlib.h> | ||
16 | #include <signal.h> | ||
17 | #include <sys/types.h> | ||
18 | #include <sys/wait.h> | ||
19 | |||
20 | |||
21 | /* Breakpoint access modes */ | ||
22 | enum { | ||
23 | BP_X = 1, | ||
24 | BP_RW = 2, | ||
25 | BP_W = 4, | ||
26 | }; | ||
27 | |||
28 | static pid_t child_pid; | ||
29 | |||
30 | /* | ||
31 | * Ensures the child and parent are always "talking" about | ||
32 | * the same test sequence. (ie: that we haven't forgotten | ||
33 | * to call check_trapped() somewhere). | ||
34 | */ | ||
35 | static int nr_tests; | ||
36 | |||
37 | static void set_breakpoint_addr(void *addr, int n) | ||
38 | { | ||
39 | int ret; | ||
40 | |||
41 | ret = ptrace(PTRACE_POKEUSER, child_pid, | ||
42 | offsetof(struct user, u_debugreg[n]), addr); | ||
43 | if (ret) { | ||
44 | perror("Can't set breakpoint addr\n"); | ||
45 | exit(-1); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | static void toggle_breakpoint(int n, int type, int len, | ||
50 | int local, int global, int set) | ||
51 | { | ||
52 | int ret; | ||
53 | |||
54 | int xtype, xlen; | ||
55 | unsigned long vdr7, dr7; | ||
56 | |||
57 | switch (type) { | ||
58 | case BP_X: | ||
59 | xtype = 0; | ||
60 | break; | ||
61 | case BP_W: | ||
62 | xtype = 1; | ||
63 | break; | ||
64 | case BP_RW: | ||
65 | xtype = 3; | ||
66 | break; | ||
67 | } | ||
68 | |||
69 | switch (len) { | ||
70 | case 1: | ||
71 | xlen = 0; | ||
72 | break; | ||
73 | case 2: | ||
74 | xlen = 4; | ||
75 | break; | ||
76 | case 4: | ||
77 | xlen = 0xc; | ||
78 | break; | ||
79 | case 8: | ||
80 | xlen = 8; | ||
81 | break; | ||
82 | } | ||
83 | |||
84 | dr7 = ptrace(PTRACE_PEEKUSER, child_pid, | ||
85 | offsetof(struct user, u_debugreg[7]), 0); | ||
86 | |||
87 | vdr7 = (xlen | xtype) << 16; | ||
88 | vdr7 <<= 4 * n; | ||
89 | |||
90 | if (local) { | ||
91 | vdr7 |= 1 << (2 * n); | ||
92 | vdr7 |= 1 << 8; | ||
93 | } | ||
94 | if (global) { | ||
95 | vdr7 |= 2 << (2 * n); | ||
96 | vdr7 |= 1 << 9; | ||
97 | } | ||
98 | |||
99 | if (set) | ||
100 | dr7 |= vdr7; | ||
101 | else | ||
102 | dr7 &= ~vdr7; | ||
103 | |||
104 | ret = ptrace(PTRACE_POKEUSER, child_pid, | ||
105 | offsetof(struct user, u_debugreg[7]), dr7); | ||
106 | if (ret) { | ||
107 | perror("Can't set dr7"); | ||
108 | exit(-1); | ||
109 | } | ||
110 | } | ||
111 | |||
112 | /* Dummy variables to test read/write accesses */ | ||
113 | static unsigned long long dummy_var[4]; | ||
114 | |||
115 | /* Dummy functions to test execution accesses */ | ||
116 | static void dummy_func(void) { } | ||
117 | static void dummy_func1(void) { } | ||
118 | static void dummy_func2(void) { } | ||
119 | static void dummy_func3(void) { } | ||
120 | |||
121 | static void (*dummy_funcs[])(void) = { | ||
122 | dummy_func, | ||
123 | dummy_func1, | ||
124 | dummy_func2, | ||
125 | dummy_func3, | ||
126 | }; | ||
127 | |||
128 | static int trapped; | ||
129 | |||
130 | static void check_trapped(void) | ||
131 | { | ||
132 | /* | ||
133 | * If we haven't trapped, wake up the parent | ||
134 | * so that it notices the failure. | ||
135 | */ | ||
136 | if (!trapped) | ||
137 | kill(getpid(), SIGUSR1); | ||
138 | trapped = 0; | ||
139 | |||
140 | nr_tests++; | ||
141 | } | ||
142 | |||
143 | static void write_var(int len) | ||
144 | { | ||
145 | char *pcval; short *psval; int *pival; long long *plval; | ||
146 | int i; | ||
147 | |||
148 | for (i = 0; i < 4; i++) { | ||
149 | switch (len) { | ||
150 | case 1: | ||
151 | pcval = (char *)&dummy_var[i]; | ||
152 | *pcval = 0xff; | ||
153 | break; | ||
154 | case 2: | ||
155 | psval = (short *)&dummy_var[i]; | ||
156 | *psval = 0xffff; | ||
157 | break; | ||
158 | case 4: | ||
159 | pival = (int *)&dummy_var[i]; | ||
160 | *pival = 0xffffffff; | ||
161 | break; | ||
162 | case 8: | ||
163 | plval = (long long *)&dummy_var[i]; | ||
164 | *plval = 0xffffffffffffffffLL; | ||
165 | break; | ||
166 | } | ||
167 | check_trapped(); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | static void read_var(int len) | ||
172 | { | ||
173 | char cval; short sval; int ival; long long lval; | ||
174 | int i; | ||
175 | |||
176 | for (i = 0; i < 4; i++) { | ||
177 | switch (len) { | ||
178 | case 1: | ||
179 | cval = *(char *)&dummy_var[i]; | ||
180 | break; | ||
181 | case 2: | ||
182 | sval = *(short *)&dummy_var[i]; | ||
183 | break; | ||
184 | case 4: | ||
185 | ival = *(int *)&dummy_var[i]; | ||
186 | break; | ||
187 | case 8: | ||
188 | lval = *(long long *)&dummy_var[i]; | ||
189 | break; | ||
190 | } | ||
191 | check_trapped(); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | /* | ||
196 | * Do the r/w/x accesses to trigger the breakpoints. And run | ||
197 | * the usual traps. | ||
198 | */ | ||
199 | static void trigger_tests(void) | ||
200 | { | ||
201 | int len, local, global, i; | ||
202 | char val; | ||
203 | int ret; | ||
204 | |||
205 | ret = ptrace(PTRACE_TRACEME, 0, NULL, 0); | ||
206 | if (ret) { | ||
207 | perror("Can't be traced?\n"); | ||
208 | return; | ||
209 | } | ||
210 | |||
211 | /* Wake up father so that it sets up the first test */ | ||
212 | kill(getpid(), SIGUSR1); | ||
213 | |||
214 | /* Test instruction breakpoints */ | ||
215 | for (local = 0; local < 2; local++) { | ||
216 | for (global = 0; global < 2; global++) { | ||
217 | if (!local && !global) | ||
218 | continue; | ||
219 | |||
220 | for (i = 0; i < 4; i++) { | ||
221 | dummy_funcs[i](); | ||
222 | check_trapped(); | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | |||
227 | /* Test write watchpoints */ | ||
228 | for (len = 1; len <= sizeof(long); len <<= 1) { | ||
229 | for (local = 0; local < 2; local++) { | ||
230 | for (global = 0; global < 2; global++) { | ||
231 | if (!local && !global) | ||
232 | continue; | ||
233 | write_var(len); | ||
234 | } | ||
235 | } | ||
236 | } | ||
237 | |||
238 | /* Test read/write watchpoints (on read accesses) */ | ||
239 | for (len = 1; len <= sizeof(long); len <<= 1) { | ||
240 | for (local = 0; local < 2; local++) { | ||
241 | for (global = 0; global < 2; global++) { | ||
242 | if (!local && !global) | ||
243 | continue; | ||
244 | read_var(len); | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | |||
249 | /* Icebp trap */ | ||
250 | asm(".byte 0xf1\n"); | ||
251 | check_trapped(); | ||
252 | |||
253 | /* Int 3 trap */ | ||
254 | asm("int $3\n"); | ||
255 | check_trapped(); | ||
256 | |||
257 | kill(getpid(), SIGUSR1); | ||
258 | } | ||
259 | |||
260 | static void check_success(const char *msg) | ||
261 | { | ||
262 | const char *msg2; | ||
263 | int child_nr_tests; | ||
264 | int status; | ||
265 | |||
266 | /* Wait for the child to SIGTRAP */ | ||
267 | wait(&status); | ||
268 | |||
269 | msg2 = "Failed"; | ||
270 | |||
271 | if (WSTOPSIG(status) == SIGTRAP) { | ||
272 | child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid, | ||
273 | &nr_tests, 0); | ||
274 | if (child_nr_tests == nr_tests) | ||
275 | msg2 = "Ok"; | ||
276 | if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1)) { | ||
277 | perror("Can't poke\n"); | ||
278 | exit(-1); | ||
279 | } | ||
280 | } | ||
281 | |||
282 | nr_tests++; | ||
283 | |||
284 | printf("%s [%s]\n", msg, msg2); | ||
285 | } | ||
286 | |||
287 | static void launch_instruction_breakpoints(char *buf, int local, int global) | ||
288 | { | ||
289 | int i; | ||
290 | |||
291 | for (i = 0; i < 4; i++) { | ||
292 | set_breakpoint_addr(dummy_funcs[i], i); | ||
293 | toggle_breakpoint(i, BP_X, 1, local, global, 1); | ||
294 | ptrace(PTRACE_CONT, child_pid, NULL, 0); | ||
295 | sprintf(buf, "Test breakpoint %d with local: %d global: %d", | ||
296 | i, local, global); | ||
297 | check_success(buf); | ||
298 | toggle_breakpoint(i, BP_X, 1, local, global, 0); | ||
299 | } | ||
300 | } | ||
301 | |||
302 | static void launch_watchpoints(char *buf, int mode, int len, | ||
303 | int local, int global) | ||
304 | { | ||
305 | const char *mode_str; | ||
306 | int i; | ||
307 | |||
308 | if (mode == BP_W) | ||
309 | mode_str = "write"; | ||
310 | else | ||
311 | mode_str = "read"; | ||
312 | |||
313 | for (i = 0; i < 4; i++) { | ||
314 | set_breakpoint_addr(&dummy_var[i], i); | ||
315 | toggle_breakpoint(i, mode, len, local, global, 1); | ||
316 | ptrace(PTRACE_CONT, child_pid, NULL, 0); | ||
317 | sprintf(buf, "Test %s watchpoint %d with len: %d local: " | ||
318 | "%d global: %d", mode_str, i, len, local, global); | ||
319 | check_success(buf); | ||
320 | toggle_breakpoint(i, mode, len, local, global, 0); | ||
321 | } | ||
322 | } | ||
323 | |||
324 | /* Set the breakpoints and check the child successfully trigger them */ | ||
325 | static void launch_tests(void) | ||
326 | { | ||
327 | char buf[1024]; | ||
328 | int len, local, global, i; | ||
329 | |||
330 | /* Instruction breakpoints */ | ||
331 | for (local = 0; local < 2; local++) { | ||
332 | for (global = 0; global < 2; global++) { | ||
333 | if (!local && !global) | ||
334 | continue; | ||
335 | launch_instruction_breakpoints(buf, local, global); | ||
336 | } | ||
337 | } | ||
338 | |||
339 | /* Write watchpoint */ | ||
340 | for (len = 1; len <= sizeof(long); len <<= 1) { | ||
341 | for (local = 0; local < 2; local++) { | ||
342 | for (global = 0; global < 2; global++) { | ||
343 | if (!local && !global) | ||
344 | continue; | ||
345 | launch_watchpoints(buf, BP_W, len, | ||
346 | local, global); | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /* Read-Write watchpoint */ | ||
352 | for (len = 1; len <= sizeof(long); len <<= 1) { | ||
353 | for (local = 0; local < 2; local++) { | ||
354 | for (global = 0; global < 2; global++) { | ||
355 | if (!local && !global) | ||
356 | continue; | ||
357 | launch_watchpoints(buf, BP_RW, len, | ||
358 | local, global); | ||
359 | } | ||
360 | } | ||
361 | } | ||
362 | |||
363 | /* Icebp traps */ | ||
364 | ptrace(PTRACE_CONT, child_pid, NULL, 0); | ||
365 | check_success("Test icebp"); | ||
366 | |||
367 | /* Int 3 traps */ | ||
368 | ptrace(PTRACE_CONT, child_pid, NULL, 0); | ||
369 | check_success("Test int 3 trap"); | ||
370 | |||
371 | ptrace(PTRACE_CONT, child_pid, NULL, 0); | ||
372 | } | ||
373 | |||
374 | int main(int argc, char **argv) | ||
375 | { | ||
376 | pid_t pid; | ||
377 | int ret; | ||
378 | |||
379 | pid = fork(); | ||
380 | if (!pid) { | ||
381 | trigger_tests(); | ||
382 | return 0; | ||
383 | } | ||
384 | |||
385 | child_pid = pid; | ||
386 | |||
387 | wait(NULL); | ||
388 | |||
389 | launch_tests(); | ||
390 | |||
391 | wait(NULL); | ||
392 | |||
393 | return 0; | ||
394 | } | ||
diff --git a/tools/testing/selftests/run_tests b/tools/testing/selftests/run_tests new file mode 100644 index 000000000000..320718a4e6bf --- /dev/null +++ b/tools/testing/selftests/run_tests | |||
@@ -0,0 +1,8 @@ | |||
1 | #!/bin/bash | ||
2 | |||
3 | TARGETS=breakpoints | ||
4 | |||
5 | for TARGET in $TARGETS | ||
6 | do | ||
7 | $TARGET/run_test | ||
8 | done | ||