1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Ptrace test for hw breakpoints |
5 | * |
6 | * Based on tools/testing/selftests/breakpoints/breakpoint_test.c |
7 | * |
8 | * This test forks and the parent then traces the child doing various |
9 | * types of ptrace enabled breakpoints |
10 | * |
11 | * Copyright (C) 2018 Michael Neuling, IBM Corporation. |
12 | */ |
13 | |
14 | #include <sys/ptrace.h> |
15 | #include <unistd.h> |
16 | #include <stddef.h> |
17 | #include <sys/user.h> |
18 | #include <stdio.h> |
19 | #include <stdlib.h> |
20 | #include <signal.h> |
21 | #include <sys/types.h> |
22 | #include <sys/wait.h> |
23 | #include <sys/syscall.h> |
24 | #include <linux/limits.h> |
25 | #include "ptrace.h" |
26 | #include "reg.h" |
27 | |
28 | #define SPRN_PVR 0x11F |
29 | #define PVR_8xx 0x00500000 |
30 | |
31 | bool is_8xx; |
32 | |
33 | /* |
34 | * Use volatile on all global var so that compiler doesn't |
35 | * optimise their load/stores. Otherwise selftest can fail. |
36 | */ |
37 | static volatile __u64 glvar; |
38 | |
39 | #define DAWR_MAX_LEN 512 |
40 | static volatile __u8 big_var[DAWR_MAX_LEN] __attribute__((aligned(512))); |
41 | |
42 | #define A_LEN 6 |
43 | #define B_LEN 6 |
44 | struct gstruct { |
45 | __u8 a[A_LEN]; /* double word aligned */ |
46 | __u8 b[B_LEN]; /* double word unaligned */ |
47 | }; |
48 | static volatile struct gstruct gstruct __attribute__((aligned(512))); |
49 | |
50 | static volatile char cwd[PATH_MAX] __attribute__((aligned(8))); |
51 | |
52 | static void get_dbginfo(pid_t child_pid, struct ppc_debug_info *dbginfo) |
53 | { |
54 | if (ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, dbginfo)) { |
55 | perror("Can't get breakpoint info" ); |
56 | exit(-1); |
57 | } |
58 | } |
59 | |
60 | static bool dawr_present(struct ppc_debug_info *dbginfo) |
61 | { |
62 | return !!(dbginfo->features & PPC_DEBUG_FEATURE_DATA_BP_DAWR); |
63 | } |
64 | |
65 | static void write_var(int len) |
66 | { |
67 | volatile __u8 *pcvar; |
68 | volatile __u16 *psvar; |
69 | volatile __u32 *pivar; |
70 | volatile __u64 *plvar; |
71 | |
72 | switch (len) { |
73 | case 1: |
74 | pcvar = (volatile __u8 *)&glvar; |
75 | *pcvar = 0xff; |
76 | break; |
77 | case 2: |
78 | psvar = (volatile __u16 *)&glvar; |
79 | *psvar = 0xffff; |
80 | break; |
81 | case 4: |
82 | pivar = (volatile __u32 *)&glvar; |
83 | *pivar = 0xffffffff; |
84 | break; |
85 | case 8: |
86 | plvar = (volatile __u64 *)&glvar; |
87 | *plvar = 0xffffffffffffffffLL; |
88 | break; |
89 | } |
90 | } |
91 | |
92 | static void read_var(int len) |
93 | { |
94 | __u8 cvar __attribute__((unused)); |
95 | __u16 svar __attribute__((unused)); |
96 | __u32 ivar __attribute__((unused)); |
97 | __u64 lvar __attribute__((unused)); |
98 | |
99 | switch (len) { |
100 | case 1: |
101 | cvar = (volatile __u8)glvar; |
102 | break; |
103 | case 2: |
104 | svar = (volatile __u16)glvar; |
105 | break; |
106 | case 4: |
107 | ivar = (volatile __u32)glvar; |
108 | break; |
109 | case 8: |
110 | lvar = (volatile __u64)glvar; |
111 | break; |
112 | } |
113 | } |
114 | |
115 | static void test_workload(void) |
116 | { |
117 | __u8 cvar __attribute__((unused)); |
118 | __u32 ivar __attribute__((unused)); |
119 | int len = 0; |
120 | |
121 | if (ptrace(PTRACE_TRACEME, 0, NULL, 0)) { |
122 | perror("Child can't be traced?" ); |
123 | exit(-1); |
124 | } |
125 | |
126 | /* Wake up father so that it sets up the first test */ |
127 | kill(getpid(), SIGUSR1); |
128 | |
129 | /* PTRACE_SET_DEBUGREG, WO test */ |
130 | for (len = 1; len <= sizeof(glvar); len <<= 1) |
131 | write_var(len); |
132 | |
133 | /* PTRACE_SET_DEBUGREG, RO test */ |
134 | for (len = 1; len <= sizeof(glvar); len <<= 1) |
135 | read_var(len); |
136 | |
137 | /* PTRACE_SET_DEBUGREG, RW test */ |
138 | for (len = 1; len <= sizeof(glvar); len <<= 1) { |
139 | if (rand() % 2) |
140 | read_var(len); |
141 | else |
142 | write_var(len); |
143 | } |
144 | |
145 | /* PTRACE_SET_DEBUGREG, Kernel Access Userspace test */ |
146 | syscall(__NR_getcwd, &cwd, PATH_MAX); |
147 | |
148 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, WO test */ |
149 | write_var(len: 1); |
150 | |
151 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RO test */ |
152 | read_var(len: 1); |
153 | |
154 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RW test */ |
155 | if (rand() % 2) |
156 | write_var(len: 1); |
157 | else |
158 | read_var(len: 1); |
159 | |
160 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, Kernel Access Userspace test */ |
161 | syscall(__NR_getcwd, &cwd, PATH_MAX); |
162 | |
163 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, WO test */ |
164 | gstruct.a[rand() % A_LEN] = 'a'; |
165 | |
166 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RO test */ |
167 | cvar = gstruct.a[rand() % A_LEN]; |
168 | |
169 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RW test */ |
170 | if (rand() % 2) |
171 | gstruct.a[rand() % A_LEN] = 'a'; |
172 | else |
173 | cvar = gstruct.a[rand() % A_LEN]; |
174 | |
175 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, WO test */ |
176 | gstruct.b[rand() % B_LEN] = 'b'; |
177 | |
178 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RO test */ |
179 | cvar = gstruct.b[rand() % B_LEN]; |
180 | |
181 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RW test */ |
182 | if (rand() % 2) |
183 | gstruct.b[rand() % B_LEN] = 'b'; |
184 | else |
185 | cvar = gstruct.b[rand() % B_LEN]; |
186 | |
187 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, DAR OUTSIDE, RW test */ |
188 | if (rand() % 2) |
189 | *((int *)(gstruct.a + 4)) = 10; |
190 | else |
191 | ivar = *((int *)(gstruct.a + 4)); |
192 | |
193 | /* PPC_PTRACE_SETHWDEBUG. DAWR_MAX_LEN. RW test */ |
194 | if (rand() % 2) |
195 | big_var[rand() % DAWR_MAX_LEN] = 'a'; |
196 | else |
197 | cvar = big_var[rand() % DAWR_MAX_LEN]; |
198 | |
199 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DW ALIGNED, WO test */ |
200 | gstruct.a[rand() % A_LEN] = 'a'; |
201 | |
202 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DW UNALIGNED, RO test */ |
203 | cvar = gstruct.b[rand() % B_LEN]; |
204 | |
205 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DAWR Overlap, WO test */ |
206 | gstruct.a[rand() % A_LEN] = 'a'; |
207 | |
208 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DAWR Overlap, RO test */ |
209 | cvar = gstruct.a[rand() % A_LEN]; |
210 | } |
211 | |
212 | static void check_success(pid_t child_pid, const char *name, const char *type, |
213 | unsigned long saddr, int len) |
214 | { |
215 | int status; |
216 | siginfo_t siginfo; |
217 | unsigned long eaddr = (saddr + len - 1) | 0x7; |
218 | |
219 | saddr &= ~0x7; |
220 | |
221 | /* Wait for the child to SIGTRAP */ |
222 | wait(&status); |
223 | |
224 | ptrace(PTRACE_GETSIGINFO, child_pid, NULL, &siginfo); |
225 | |
226 | if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP || |
227 | (unsigned long)siginfo.si_addr < saddr || |
228 | (unsigned long)siginfo.si_addr > eaddr) { |
229 | printf("%s, %s, len: %d: Fail\n" , name, type, len); |
230 | exit(-1); |
231 | } |
232 | |
233 | printf("%s, %s, len: %d: Ok\n" , name, type, len); |
234 | |
235 | if (!is_8xx) { |
236 | /* |
237 | * For ptrace registered watchpoint, signal is generated |
238 | * before executing load/store. Singlestep the instruction |
239 | * and then continue the test. |
240 | */ |
241 | ptrace(PTRACE_SINGLESTEP, child_pid, NULL, 0); |
242 | wait(NULL); |
243 | } |
244 | } |
245 | |
246 | static void ptrace_set_debugreg(pid_t child_pid, unsigned long wp_addr) |
247 | { |
248 | if (ptrace(PTRACE_SET_DEBUGREG, child_pid, 0, wp_addr)) { |
249 | perror("PTRACE_SET_DEBUGREG failed" ); |
250 | exit(-1); |
251 | } |
252 | } |
253 | |
254 | static int ptrace_sethwdebug(pid_t child_pid, struct ppc_hw_breakpoint *info) |
255 | { |
256 | int wh = ptrace(PPC_PTRACE_SETHWDEBUG, child_pid, 0, info); |
257 | |
258 | if (wh <= 0) { |
259 | perror("PPC_PTRACE_SETHWDEBUG failed" ); |
260 | exit(-1); |
261 | } |
262 | return wh; |
263 | } |
264 | |
265 | static void ptrace_delhwdebug(pid_t child_pid, int wh) |
266 | { |
267 | if (ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, wh) < 0) { |
268 | perror("PPC_PTRACE_DELHWDEBUG failed" ); |
269 | exit(-1); |
270 | } |
271 | } |
272 | |
273 | #define DABR_READ_SHIFT 0 |
274 | #define DABR_WRITE_SHIFT 1 |
275 | #define DABR_TRANSLATION_SHIFT 2 |
276 | |
277 | static int test_set_debugreg(pid_t child_pid) |
278 | { |
279 | unsigned long wp_addr = (unsigned long)&glvar; |
280 | char *name = "PTRACE_SET_DEBUGREG" ; |
281 | int len; |
282 | |
283 | /* PTRACE_SET_DEBUGREG, WO test*/ |
284 | wp_addr &= ~0x7UL; |
285 | wp_addr |= (1UL << DABR_WRITE_SHIFT); |
286 | wp_addr |= (1UL << DABR_TRANSLATION_SHIFT); |
287 | for (len = 1; len <= sizeof(glvar); len <<= 1) { |
288 | ptrace_set_debugreg(child_pid, wp_addr); |
289 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
290 | check_success(child_pid, name, type: "WO" , saddr: wp_addr, len); |
291 | } |
292 | |
293 | /* PTRACE_SET_DEBUGREG, RO test */ |
294 | wp_addr &= ~0x7UL; |
295 | wp_addr |= (1UL << DABR_READ_SHIFT); |
296 | wp_addr |= (1UL << DABR_TRANSLATION_SHIFT); |
297 | for (len = 1; len <= sizeof(glvar); len <<= 1) { |
298 | ptrace_set_debugreg(child_pid, wp_addr); |
299 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
300 | check_success(child_pid, name, type: "RO" , saddr: wp_addr, len); |
301 | } |
302 | |
303 | /* PTRACE_SET_DEBUGREG, RW test */ |
304 | wp_addr &= ~0x7UL; |
305 | wp_addr |= (1Ul << DABR_READ_SHIFT); |
306 | wp_addr |= (1UL << DABR_WRITE_SHIFT); |
307 | wp_addr |= (1UL << DABR_TRANSLATION_SHIFT); |
308 | for (len = 1; len <= sizeof(glvar); len <<= 1) { |
309 | ptrace_set_debugreg(child_pid, wp_addr); |
310 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
311 | check_success(child_pid, name, type: "RW" , saddr: wp_addr, len); |
312 | } |
313 | |
314 | ptrace_set_debugreg(child_pid, wp_addr: 0); |
315 | return 0; |
316 | } |
317 | |
318 | static int test_set_debugreg_kernel_userspace(pid_t child_pid) |
319 | { |
320 | unsigned long wp_addr = (unsigned long)cwd; |
321 | char *name = "PTRACE_SET_DEBUGREG" ; |
322 | |
323 | /* PTRACE_SET_DEBUGREG, Kernel Access Userspace test */ |
324 | wp_addr &= ~0x7UL; |
325 | wp_addr |= (1Ul << DABR_READ_SHIFT); |
326 | wp_addr |= (1UL << DABR_WRITE_SHIFT); |
327 | wp_addr |= (1UL << DABR_TRANSLATION_SHIFT); |
328 | ptrace_set_debugreg(child_pid, wp_addr); |
329 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
330 | check_success(child_pid, name, type: "Kernel Access Userspace" , saddr: wp_addr, len: 8); |
331 | |
332 | ptrace_set_debugreg(child_pid, wp_addr: 0); |
333 | return 0; |
334 | } |
335 | |
336 | static void get_ppc_hw_breakpoint(struct ppc_hw_breakpoint *info, int type, |
337 | unsigned long addr, int len) |
338 | { |
339 | info->version = 1; |
340 | info->trigger_type = type; |
341 | info->condition_mode = PPC_BREAKPOINT_CONDITION_NONE; |
342 | info->addr = (__u64)addr; |
343 | info->addr2 = (__u64)addr + len; |
344 | info->condition_value = 0; |
345 | if (!len) |
346 | info->addr_mode = PPC_BREAKPOINT_MODE_EXACT; |
347 | else |
348 | info->addr_mode = PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE; |
349 | } |
350 | |
351 | static void test_sethwdebug_exact(pid_t child_pid) |
352 | { |
353 | struct ppc_hw_breakpoint info; |
354 | unsigned long wp_addr = (unsigned long)&glvar; |
355 | char *name = "PPC_PTRACE_SETHWDEBUG, MODE_EXACT" ; |
356 | int len = 1; /* hardcoded in kernel */ |
357 | int wh; |
358 | |
359 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, WO test */ |
360 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_WRITE, addr: wp_addr, len: 0); |
361 | wh = ptrace_sethwdebug(child_pid, info: &info); |
362 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
363 | check_success(child_pid, name, type: "WO" , saddr: wp_addr, len); |
364 | ptrace_delhwdebug(child_pid, wh); |
365 | |
366 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RO test */ |
367 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_READ, addr: wp_addr, len: 0); |
368 | wh = ptrace_sethwdebug(child_pid, info: &info); |
369 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
370 | check_success(child_pid, name, type: "RO" , saddr: wp_addr, len); |
371 | ptrace_delhwdebug(child_pid, wh); |
372 | |
373 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RW test */ |
374 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_RW, addr: wp_addr, len: 0); |
375 | wh = ptrace_sethwdebug(child_pid, info: &info); |
376 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
377 | check_success(child_pid, name, type: "RW" , saddr: wp_addr, len); |
378 | ptrace_delhwdebug(child_pid, wh); |
379 | } |
380 | |
381 | static void test_sethwdebug_exact_kernel_userspace(pid_t child_pid) |
382 | { |
383 | struct ppc_hw_breakpoint info; |
384 | unsigned long wp_addr = (unsigned long)&cwd; |
385 | char *name = "PPC_PTRACE_SETHWDEBUG, MODE_EXACT" ; |
386 | int len = 1; /* hardcoded in kernel */ |
387 | int wh; |
388 | |
389 | /* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, Kernel Access Userspace test */ |
390 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_WRITE, addr: wp_addr, len: 0); |
391 | wh = ptrace_sethwdebug(child_pid, info: &info); |
392 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
393 | check_success(child_pid, name, type: "Kernel Access Userspace" , saddr: wp_addr, len); |
394 | ptrace_delhwdebug(child_pid, wh); |
395 | } |
396 | |
397 | static void test_sethwdebug_range_aligned(pid_t child_pid) |
398 | { |
399 | struct ppc_hw_breakpoint info; |
400 | unsigned long wp_addr; |
401 | char *name = "PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED" ; |
402 | int len; |
403 | int wh; |
404 | |
405 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, WO test */ |
406 | wp_addr = (unsigned long)&gstruct.a; |
407 | len = A_LEN; |
408 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_WRITE, addr: wp_addr, len); |
409 | wh = ptrace_sethwdebug(child_pid, info: &info); |
410 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
411 | check_success(child_pid, name, type: "WO" , saddr: wp_addr, len); |
412 | ptrace_delhwdebug(child_pid, wh); |
413 | |
414 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RO test */ |
415 | wp_addr = (unsigned long)&gstruct.a; |
416 | len = A_LEN; |
417 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_READ, addr: wp_addr, len); |
418 | wh = ptrace_sethwdebug(child_pid, info: &info); |
419 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
420 | check_success(child_pid, name, type: "RO" , saddr: wp_addr, len); |
421 | ptrace_delhwdebug(child_pid, wh); |
422 | |
423 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RW test */ |
424 | wp_addr = (unsigned long)&gstruct.a; |
425 | len = A_LEN; |
426 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_RW, addr: wp_addr, len); |
427 | wh = ptrace_sethwdebug(child_pid, info: &info); |
428 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
429 | check_success(child_pid, name, type: "RW" , saddr: wp_addr, len); |
430 | ptrace_delhwdebug(child_pid, wh); |
431 | } |
432 | |
433 | static void test_multi_sethwdebug_range(pid_t child_pid) |
434 | { |
435 | struct ppc_hw_breakpoint info1, info2; |
436 | unsigned long wp_addr1, wp_addr2; |
437 | char *name1 = "PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DW ALIGNED" ; |
438 | char *name2 = "PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DW UNALIGNED" ; |
439 | int len1, len2; |
440 | int wh1, wh2; |
441 | |
442 | wp_addr1 = (unsigned long)&gstruct.a; |
443 | wp_addr2 = (unsigned long)&gstruct.b; |
444 | len1 = A_LEN; |
445 | len2 = B_LEN; |
446 | get_ppc_hw_breakpoint(info: &info1, type: PPC_BREAKPOINT_TRIGGER_WRITE, addr: wp_addr1, len: len1); |
447 | get_ppc_hw_breakpoint(info: &info2, type: PPC_BREAKPOINT_TRIGGER_READ, addr: wp_addr2, len: len2); |
448 | |
449 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DW ALIGNED, WO test */ |
450 | wh1 = ptrace_sethwdebug(child_pid, info: &info1); |
451 | |
452 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DW UNALIGNED, RO test */ |
453 | wh2 = ptrace_sethwdebug(child_pid, info: &info2); |
454 | |
455 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
456 | check_success(child_pid, name: name1, type: "WO" , saddr: wp_addr1, len: len1); |
457 | |
458 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
459 | check_success(child_pid, name: name2, type: "RO" , saddr: wp_addr2, len: len2); |
460 | |
461 | ptrace_delhwdebug(child_pid, wh: wh1); |
462 | ptrace_delhwdebug(child_pid, wh: wh2); |
463 | } |
464 | |
465 | static void test_multi_sethwdebug_range_dawr_overlap(pid_t child_pid) |
466 | { |
467 | struct ppc_hw_breakpoint info1, info2; |
468 | unsigned long wp_addr1, wp_addr2; |
469 | char *name = "PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DAWR Overlap" ; |
470 | int len1, len2; |
471 | int wh1, wh2; |
472 | |
473 | wp_addr1 = (unsigned long)&gstruct.a; |
474 | wp_addr2 = (unsigned long)&gstruct.a; |
475 | len1 = A_LEN; |
476 | len2 = A_LEN; |
477 | get_ppc_hw_breakpoint(info: &info1, type: PPC_BREAKPOINT_TRIGGER_WRITE, addr: wp_addr1, len: len1); |
478 | get_ppc_hw_breakpoint(info: &info2, type: PPC_BREAKPOINT_TRIGGER_READ, addr: wp_addr2, len: len2); |
479 | |
480 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DAWR Overlap, WO test */ |
481 | wh1 = ptrace_sethwdebug(child_pid, info: &info1); |
482 | |
483 | /* PPC_PTRACE_SETHWDEBUG 2, MODE_RANGE, DAWR Overlap, RO test */ |
484 | wh2 = ptrace_sethwdebug(child_pid, info: &info2); |
485 | |
486 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
487 | check_success(child_pid, name, type: "WO" , saddr: wp_addr1, len: len1); |
488 | |
489 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
490 | check_success(child_pid, name, type: "RO" , saddr: wp_addr2, len: len2); |
491 | |
492 | ptrace_delhwdebug(child_pid, wh: wh1); |
493 | ptrace_delhwdebug(child_pid, wh: wh2); |
494 | } |
495 | |
496 | static void test_sethwdebug_range_unaligned(pid_t child_pid) |
497 | { |
498 | struct ppc_hw_breakpoint info; |
499 | unsigned long wp_addr; |
500 | char *name = "PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED" ; |
501 | int len; |
502 | int wh; |
503 | |
504 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, WO test */ |
505 | wp_addr = (unsigned long)&gstruct.b; |
506 | len = B_LEN; |
507 | get_ppc_hw_breakpoint(info: &info, type: PPC_BREAKPOINT_TRIGGER_WRITE, addr: wp_addr, len); |
508 | wh = ptrace_sethwdebug(child_pid, info: &info); |
509 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
510 | check_success(child_pid, name, type: "WO" , saddr: wp_addr, len); |
511 | ptrace_delhwdebug(child_pid, wh); |
512 | |
513 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RO test */ |
514 | wp_addr = (unsigned long)&gstruct.b; |
515 | len = B_LEN; |
516 | get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_READ, wp_addr, len); |
517 | wh = ptrace_sethwdebug(child_pid, info: &info); |
518 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
519 | check_success(child_pid, name, type: "RO" , saddr: wp_addr, len); |
520 | ptrace_delhwdebug(child_pid, wh); |
521 | |
522 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RW test */ |
523 | wp_addr = (unsigned long)&gstruct.b; |
524 | len = B_LEN; |
525 | get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, len); |
526 | wh = ptrace_sethwdebug(child_pid, info: &info); |
527 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
528 | check_success(child_pid, name, type: "RW" , saddr: wp_addr, len); |
529 | ptrace_delhwdebug(child_pid, wh); |
530 | |
531 | } |
532 | |
533 | static void test_sethwdebug_range_unaligned_dar(pid_t child_pid) |
534 | { |
535 | struct ppc_hw_breakpoint info; |
536 | unsigned long wp_addr; |
537 | char *name = "PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, DAR OUTSIDE" ; |
538 | int len; |
539 | int wh; |
540 | |
541 | /* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, DAR OUTSIDE, RW test */ |
542 | wp_addr = (unsigned long)&gstruct.b; |
543 | len = B_LEN; |
544 | get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_WRITE, wp_addr, len); |
545 | wh = ptrace_sethwdebug(child_pid, info: &info); |
546 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
547 | check_success(child_pid, name, type: "RW" , saddr: wp_addr, len); |
548 | ptrace_delhwdebug(child_pid, wh); |
549 | } |
550 | |
551 | static void test_sethwdebug_dawr_max_range(pid_t child_pid) |
552 | { |
553 | struct ppc_hw_breakpoint info; |
554 | unsigned long wp_addr; |
555 | char *name = "PPC_PTRACE_SETHWDEBUG, DAWR_MAX_LEN" ; |
556 | int len; |
557 | int wh; |
558 | |
559 | /* PPC_PTRACE_SETHWDEBUG, DAWR_MAX_LEN, RW test */ |
560 | wp_addr = (unsigned long)big_var; |
561 | len = DAWR_MAX_LEN; |
562 | get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, len); |
563 | wh = ptrace_sethwdebug(child_pid, info: &info); |
564 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
565 | check_success(child_pid, name, type: "RW" , saddr: wp_addr, len); |
566 | ptrace_delhwdebug(child_pid, wh); |
567 | } |
568 | |
569 | /* Set the breakpoints and check the child successfully trigger them */ |
570 | static void |
571 | run_tests(pid_t child_pid, struct ppc_debug_info *dbginfo, bool dawr) |
572 | { |
573 | test_set_debugreg(child_pid); |
574 | test_set_debugreg_kernel_userspace(child_pid); |
575 | test_sethwdebug_exact(child_pid); |
576 | test_sethwdebug_exact_kernel_userspace(child_pid); |
577 | if (dbginfo->features & PPC_DEBUG_FEATURE_DATA_BP_RANGE) { |
578 | test_sethwdebug_range_aligned(child_pid); |
579 | if (dawr || is_8xx) { |
580 | test_sethwdebug_range_unaligned(child_pid); |
581 | test_sethwdebug_range_unaligned_dar(child_pid); |
582 | test_sethwdebug_dawr_max_range(child_pid); |
583 | if (dbginfo->num_data_bps > 1) { |
584 | test_multi_sethwdebug_range(child_pid); |
585 | test_multi_sethwdebug_range_dawr_overlap(child_pid); |
586 | } |
587 | } |
588 | } |
589 | } |
590 | |
591 | static int ptrace_hwbreak(void) |
592 | { |
593 | pid_t child_pid; |
594 | struct ppc_debug_info dbginfo; |
595 | bool dawr; |
596 | |
597 | child_pid = fork(); |
598 | if (!child_pid) { |
599 | test_workload(); |
600 | return 0; |
601 | } |
602 | |
603 | wait(NULL); |
604 | |
605 | get_dbginfo(child_pid, dbginfo: &dbginfo); |
606 | SKIP_IF_MSG(dbginfo.num_data_bps == 0, "No data breakpoints present" ); |
607 | |
608 | dawr = dawr_present(dbginfo: &dbginfo); |
609 | run_tests(child_pid, dbginfo: &dbginfo, dawr); |
610 | |
611 | /* Let the child exit first. */ |
612 | ptrace(PTRACE_CONT, child_pid, NULL, 0); |
613 | wait(NULL); |
614 | |
615 | /* |
616 | * Testcases exits immediately with -1 on any failure. If |
617 | * it has reached here, it means all tests were successful. |
618 | */ |
619 | return TEST_PASS; |
620 | } |
621 | |
622 | int main(int argc, char **argv, char **envp) |
623 | { |
624 | is_8xx = mfspr(SPRN_PVR) == PVR_8xx; |
625 | |
626 | return test_harness(ptrace_hwbreak, "ptrace-hwbreak" ); |
627 | } |
628 | |