1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2016 Google, Inc. |
4 | * |
5 | * Original Code by Pavel Labath <labath@google.com> |
6 | * |
7 | * Code modified by Pratyush Anand <panand@redhat.com> |
8 | * for testing different byte select for each access size. |
9 | */ |
10 | |
11 | #define _GNU_SOURCE |
12 | |
13 | #include <asm/ptrace.h> |
14 | #include <sys/types.h> |
15 | #include <sys/wait.h> |
16 | #include <sys/ptrace.h> |
17 | #include <sys/param.h> |
18 | #include <sys/uio.h> |
19 | #include <stdint.h> |
20 | #include <stdbool.h> |
21 | #include <stddef.h> |
22 | #include <string.h> |
23 | #include <stdio.h> |
24 | #include <unistd.h> |
25 | #include <elf.h> |
26 | #include <errno.h> |
27 | #include <signal.h> |
28 | |
29 | #include "../kselftest.h" |
30 | |
31 | static volatile uint8_t var[96] __attribute__((__aligned__(32))); |
32 | |
33 | static void child(int size, int wr) |
34 | { |
35 | volatile uint8_t *addr = &var[32 + wr]; |
36 | |
37 | if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { |
38 | ksft_print_msg( |
39 | msg: "ptrace(PTRACE_TRACEME) failed: %s\n" , |
40 | strerror(errno)); |
41 | _exit(1); |
42 | } |
43 | |
44 | if (raise(SIGSTOP) != 0) { |
45 | ksft_print_msg( |
46 | msg: "raise(SIGSTOP) failed: %s\n" , strerror(errno)); |
47 | _exit(1); |
48 | } |
49 | |
50 | if ((uintptr_t) addr % size) { |
51 | ksft_print_msg( |
52 | msg: "Wrong address write for the given size: %s\n" , |
53 | strerror(errno)); |
54 | _exit(1); |
55 | } |
56 | |
57 | switch (size) { |
58 | case 1: |
59 | *addr = 47; |
60 | break; |
61 | case 2: |
62 | *(uint16_t *)addr = 47; |
63 | break; |
64 | case 4: |
65 | *(uint32_t *)addr = 47; |
66 | break; |
67 | case 8: |
68 | *(uint64_t *)addr = 47; |
69 | break; |
70 | case 16: |
71 | __asm__ volatile ("stp x29, x30, %0" : "=m" (addr[0])); |
72 | break; |
73 | case 32: |
74 | __asm__ volatile ("stp q29, q30, %0" : "=m" (addr[0])); |
75 | break; |
76 | } |
77 | |
78 | _exit(0); |
79 | } |
80 | |
81 | static bool set_watchpoint(pid_t pid, int size, int wp) |
82 | { |
83 | const volatile uint8_t *addr = &var[32 + wp]; |
84 | const int offset = (uintptr_t)addr % 8; |
85 | const unsigned int byte_mask = ((1 << size) - 1) << offset; |
86 | const unsigned int type = 2; /* Write */ |
87 | const unsigned int enable = 1; |
88 | const unsigned int control = byte_mask << 5 | type << 3 | enable; |
89 | struct user_hwdebug_state dreg_state; |
90 | struct iovec iov; |
91 | |
92 | memset(&dreg_state, 0, sizeof(dreg_state)); |
93 | dreg_state.dbg_regs[0].addr = (uintptr_t)(addr - offset); |
94 | dreg_state.dbg_regs[0].ctrl = control; |
95 | iov.iov_base = &dreg_state; |
96 | iov.iov_len = offsetof(struct user_hwdebug_state, dbg_regs) + |
97 | sizeof(dreg_state.dbg_regs[0]); |
98 | if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_HW_WATCH, &iov) == 0) |
99 | return true; |
100 | |
101 | if (errno == EIO) |
102 | ksft_print_msg( |
103 | msg: "ptrace(PTRACE_SETREGSET, NT_ARM_HW_WATCH) not supported on this hardware: %s\n" , |
104 | strerror(errno)); |
105 | |
106 | ksft_print_msg( |
107 | msg: "ptrace(PTRACE_SETREGSET, NT_ARM_HW_WATCH) failed: %s\n" , |
108 | strerror(errno)); |
109 | return false; |
110 | } |
111 | |
112 | static bool run_test(int wr_size, int wp_size, int wr, int wp) |
113 | { |
114 | int status; |
115 | siginfo_t siginfo; |
116 | pid_t pid = fork(); |
117 | pid_t wpid; |
118 | |
119 | if (pid < 0) { |
120 | ksft_test_result_fail( |
121 | msg: "fork() failed: %s\n" , strerror(errno)); |
122 | return false; |
123 | } |
124 | if (pid == 0) |
125 | child(size: wr_size, wr); |
126 | |
127 | wpid = waitpid(pid, &status, __WALL); |
128 | if (wpid != pid) { |
129 | ksft_print_msg( |
130 | msg: "waitpid() failed: %s\n" , strerror(errno)); |
131 | return false; |
132 | } |
133 | if (!WIFSTOPPED(status)) { |
134 | ksft_print_msg( |
135 | msg: "child did not stop: %s\n" , strerror(errno)); |
136 | return false; |
137 | } |
138 | if (WSTOPSIG(status) != SIGSTOP) { |
139 | ksft_print_msg(msg: "child did not stop with SIGSTOP\n" ); |
140 | return false; |
141 | } |
142 | |
143 | if (!set_watchpoint(pid, size: wp_size, wp)) |
144 | return false; |
145 | |
146 | if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) { |
147 | ksft_print_msg( |
148 | msg: "ptrace(PTRACE_CONT) failed: %s\n" , |
149 | strerror(errno)); |
150 | return false; |
151 | } |
152 | |
153 | alarm(3); |
154 | wpid = waitpid(pid, &status, __WALL); |
155 | if (wpid != pid) { |
156 | ksft_print_msg( |
157 | msg: "waitpid() failed: %s\n" , strerror(errno)); |
158 | return false; |
159 | } |
160 | alarm(0); |
161 | if (WIFEXITED(status)) { |
162 | ksft_print_msg(msg: "child exited prematurely\n" ); |
163 | return false; |
164 | } |
165 | if (!WIFSTOPPED(status)) { |
166 | ksft_print_msg(msg: "child did not stop\n" ); |
167 | return false; |
168 | } |
169 | if (WSTOPSIG(status) != SIGTRAP) { |
170 | ksft_print_msg(msg: "child did not stop with SIGTRAP\n" ); |
171 | return false; |
172 | } |
173 | if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo) != 0) { |
174 | ksft_print_msg( |
175 | msg: "ptrace(PTRACE_GETSIGINFO): %s\n" , |
176 | strerror(errno)); |
177 | return false; |
178 | } |
179 | if (siginfo.si_code != TRAP_HWBKPT) { |
180 | ksft_print_msg( |
181 | msg: "Unexpected si_code %d\n" , siginfo.si_code); |
182 | return false; |
183 | } |
184 | |
185 | kill(pid, SIGKILL); |
186 | wpid = waitpid(pid, &status, 0); |
187 | if (wpid != pid) { |
188 | ksft_print_msg( |
189 | msg: "waitpid() failed: %s\n" , strerror(errno)); |
190 | return false; |
191 | } |
192 | return true; |
193 | } |
194 | |
195 | static void sigalrm(int sig) |
196 | { |
197 | } |
198 | |
199 | int main(int argc, char **argv) |
200 | { |
201 | int opt; |
202 | bool succeeded = true; |
203 | struct sigaction act; |
204 | int wr, wp, size; |
205 | bool result; |
206 | |
207 | ksft_print_header(); |
208 | ksft_set_plan(plan: 213); |
209 | |
210 | act.sa_handler = sigalrm; |
211 | sigemptyset(&act.sa_mask); |
212 | act.sa_flags = 0; |
213 | sigaction(SIGALRM, &act, NULL); |
214 | for (size = 1; size <= 32; size = size*2) { |
215 | for (wr = 0; wr <= 32; wr = wr + size) { |
216 | for (wp = wr - size; wp <= wr + size; wp = wp + size) { |
217 | result = run_test(wr_size: size, wp_size: MIN(size, 8), wr, wp); |
218 | if ((result && wr == wp) || |
219 | (!result && wr != wp)) |
220 | ksft_test_result_pass( |
221 | msg: "Test size = %d write offset = %d watchpoint offset = %d\n" , |
222 | size, wr, wp); |
223 | else { |
224 | ksft_test_result_fail( |
225 | msg: "Test size = %d write offset = %d watchpoint offset = %d\n" , |
226 | size, wr, wp); |
227 | succeeded = false; |
228 | } |
229 | } |
230 | } |
231 | } |
232 | |
233 | for (size = 1; size <= 32; size = size*2) { |
234 | if (run_test(wr_size: size, wp_size: 8, wr: -size, wp: -8)) |
235 | ksft_test_result_pass( |
236 | msg: "Test size = %d write offset = %d watchpoint offset = -8\n" , |
237 | size, -size); |
238 | else { |
239 | ksft_test_result_fail( |
240 | msg: "Test size = %d write offset = %d watchpoint offset = -8\n" , |
241 | size, -size); |
242 | succeeded = false; |
243 | } |
244 | } |
245 | |
246 | if (succeeded) |
247 | ksft_exit_pass(); |
248 | else |
249 | ksft_exit_fail(); |
250 | } |
251 | |