Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1da177e4 LT |
2 | /* ptrace.c: Sparc process tracing support. |
3 | * | |
8e3fe806 | 4 | * Copyright (C) 1996, 2008 David S. Miller (davem@davemloft.net) |
1da177e4 LT |
5 | * |
6 | * Based upon code written by Ross Biro, Linus Torvalds, Bob Manson, | |
7 | * and David Mosberger. | |
8 | * | |
5b2afff2 | 9 | * Added Linux support -miguel (weird, eh?, the original code was meant |
1da177e4 LT |
10 | * to emulate SunOS). |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
14 | #include <linux/sched.h> | |
15 | #include <linux/mm.h> | |
16 | #include <linux/errno.h> | |
17 | #include <linux/ptrace.h> | |
18 | #include <linux/user.h> | |
19 | #include <linux/smp.h> | |
1da177e4 | 20 | #include <linux/security.h> |
7ed20e1a | 21 | #include <linux/signal.h> |
8e3fe806 DM |
22 | #include <linux/regset.h> |
23 | #include <linux/elf.h> | |
1c133b4b | 24 | #include <linux/tracehook.h> |
1da177e4 LT |
25 | |
26 | #include <asm/pgtable.h> | |
7c0f6ba6 | 27 | #include <linux/uaccess.h> |
d550bbd4 | 28 | #include <asm/cacheflush.h> |
1da177e4 | 29 | |
c8c8782d SR |
30 | #include "kernel.h" |
31 | ||
1da177e4 | 32 | /* #define ALLOW_INIT_TRACING */ |
1da177e4 LT |
33 | |
34 | /* | |
35 | * Called by kernel/ptrace.c when detaching.. | |
36 | * | |
37 | * Make sure single step bits etc are not set. | |
38 | */ | |
39 | void ptrace_disable(struct task_struct *child) | |
40 | { | |
41 | /* nothing to do */ | |
42 | } | |
43 | ||
8e3fe806 DM |
44 | enum sparc_regset { |
45 | REGSET_GENERAL, | |
46 | REGSET_FP, | |
47 | }; | |
48 | ||
cf51e129 AV |
49 | static int regwindow32_get(struct task_struct *target, |
50 | const struct pt_regs *regs, | |
51 | u32 *uregs) | |
52 | { | |
53 | unsigned long reg_window = regs->u_regs[UREG_I6]; | |
54 | int size = 16 * sizeof(u32); | |
55 | ||
56 | if (target == current) { | |
57 | if (copy_from_user(uregs, (void __user *)reg_window, size)) | |
58 | return -EFAULT; | |
59 | } else { | |
60 | if (access_process_vm(target, reg_window, uregs, size, | |
61 | FOLL_FORCE) != size) | |
62 | return -EFAULT; | |
63 | } | |
64 | return 0; | |
65 | } | |
66 | ||
67 | static int regwindow32_set(struct task_struct *target, | |
68 | const struct pt_regs *regs, | |
69 | u32 *uregs) | |
70 | { | |
71 | unsigned long reg_window = regs->u_regs[UREG_I6]; | |
72 | int size = 16 * sizeof(u32); | |
73 | ||
74 | if (target == current) { | |
75 | if (copy_to_user((void __user *)reg_window, uregs, size)) | |
76 | return -EFAULT; | |
77 | } else { | |
78 | if (access_process_vm(target, reg_window, uregs, size, | |
79 | FOLL_FORCE | FOLL_WRITE) != size) | |
80 | return -EFAULT; | |
81 | } | |
82 | return 0; | |
83 | } | |
84 | ||
8e3fe806 DM |
85 | static int genregs32_get(struct task_struct *target, |
86 | const struct user_regset *regset, | |
87 | unsigned int pos, unsigned int count, | |
88 | void *kbuf, void __user *ubuf) | |
89 | { | |
90 | const struct pt_regs *regs = target->thread.kregs; | |
cf51e129 AV |
91 | u32 uregs[16]; |
92 | int ret; | |
8e3fe806 DM |
93 | |
94 | if (target == current) | |
95 | flush_user_windows(); | |
96 | ||
cf51e129 AV |
97 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, |
98 | regs->u_regs, | |
99 | 0, 16 * sizeof(u32)); | |
100 | if (ret || !count) | |
101 | return ret; | |
8e3fe806 | 102 | |
cf51e129 AV |
103 | if (pos < 32 * sizeof(u32)) { |
104 | if (regwindow32_get(target, regs, uregs)) | |
8e3fe806 | 105 | return -EFAULT; |
cf51e129 AV |
106 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, |
107 | uregs, | |
108 | 16 * sizeof(u32), 32 * sizeof(u32)); | |
109 | if (ret || !count) | |
110 | return ret; | |
8e3fe806 | 111 | } |
8e3fe806 | 112 | |
cf51e129 AV |
113 | uregs[0] = regs->psr; |
114 | uregs[1] = regs->pc; | |
115 | uregs[2] = regs->npc; | |
116 | uregs[3] = regs->y; | |
117 | uregs[4] = 0; /* WIM */ | |
118 | uregs[5] = 0; /* TBR */ | |
119 | return user_regset_copyout(&pos, &count, &kbuf, &ubuf, | |
120 | uregs, | |
121 | 32 * sizeof(u32), 38 * sizeof(u32)); | |
8e3fe806 DM |
122 | } |
123 | ||
124 | static int genregs32_set(struct task_struct *target, | |
125 | const struct user_regset *regset, | |
126 | unsigned int pos, unsigned int count, | |
127 | const void *kbuf, const void __user *ubuf) | |
128 | { | |
129 | struct pt_regs *regs = target->thread.kregs; | |
cf51e129 AV |
130 | u32 uregs[16]; |
131 | u32 psr; | |
132 | int ret; | |
8e3fe806 DM |
133 | |
134 | if (target == current) | |
135 | flush_user_windows(); | |
136 | ||
cf51e129 AV |
137 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
138 | regs->u_regs, | |
139 | 0, 16 * sizeof(u32)); | |
140 | if (ret || !count) | |
141 | return ret; | |
8e3fe806 | 142 | |
cf51e129 AV |
143 | if (pos < 32 * sizeof(u32)) { |
144 | if (regwindow32_get(target, regs, uregs)) | |
8e3fe806 | 145 | return -EFAULT; |
cf51e129 AV |
146 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
147 | uregs, | |
148 | 16 * sizeof(u32), 32 * sizeof(u32)); | |
149 | if (ret) | |
150 | return ret; | |
151 | if (regwindow32_set(target, regs, uregs)) | |
152 | return -EFAULT; | |
153 | if (!count) | |
154 | return 0; | |
8e3fe806 | 155 | } |
cf51e129 AV |
156 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
157 | &psr, | |
158 | 32 * sizeof(u32), 33 * sizeof(u32)); | |
159 | if (ret) | |
160 | return ret; | |
161 | regs->psr = (regs->psr & ~(PSR_ICC | PSR_SYSCALL)) | | |
162 | (psr & (PSR_ICC | PSR_SYSCALL)); | |
163 | if (!count) | |
164 | return 0; | |
165 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
166 | ®s->pc, | |
167 | 33 * sizeof(u32), 34 * sizeof(u32)); | |
168 | if (ret || !count) | |
169 | return ret; | |
170 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
171 | ®s->y, | |
172 | 34 * sizeof(u32), 35 * sizeof(u32)); | |
173 | if (ret || !count) | |
174 | return ret; | |
8e3fe806 | 175 | return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, |
cf51e129 | 176 | 35 * sizeof(u32), 38 * sizeof(u32)); |
8e3fe806 DM |
177 | } |
178 | ||
179 | static int fpregs32_get(struct task_struct *target, | |
180 | const struct user_regset *regset, | |
181 | unsigned int pos, unsigned int count, | |
182 | void *kbuf, void __user *ubuf) | |
183 | { | |
184 | const unsigned long *fpregs = target->thread.float_regs; | |
185 | int ret = 0; | |
186 | ||
187 | #if 0 | |
188 | if (target == current) | |
189 | save_and_clear_fpu(); | |
190 | #endif | |
191 | ||
192 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, | |
193 | fpregs, | |
194 | 0, 32 * sizeof(u32)); | |
195 | ||
196 | if (!ret) | |
197 | ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, | |
198 | 32 * sizeof(u32), | |
199 | 33 * sizeof(u32)); | |
200 | if (!ret) | |
201 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, | |
202 | &target->thread.fsr, | |
203 | 33 * sizeof(u32), | |
204 | 34 * sizeof(u32)); | |
205 | ||
206 | if (!ret) { | |
207 | unsigned long val; | |
208 | ||
209 | val = (1 << 8) | (8 << 16); | |
210 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, | |
211 | &val, | |
212 | 34 * sizeof(u32), | |
213 | 35 * sizeof(u32)); | |
214 | } | |
215 | ||
216 | if (!ret) | |
217 | ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, | |
218 | 35 * sizeof(u32), -1); | |
219 | ||
220 | return ret; | |
221 | } | |
222 | ||
223 | static int fpregs32_set(struct task_struct *target, | |
224 | const struct user_regset *regset, | |
225 | unsigned int pos, unsigned int count, | |
226 | const void *kbuf, const void __user *ubuf) | |
227 | { | |
228 | unsigned long *fpregs = target->thread.float_regs; | |
229 | int ret; | |
230 | ||
231 | #if 0 | |
232 | if (target == current) | |
233 | save_and_clear_fpu(); | |
234 | #endif | |
235 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
236 | fpregs, | |
237 | 0, 32 * sizeof(u32)); | |
238 | if (!ret) | |
239 | user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, | |
240 | 32 * sizeof(u32), | |
241 | 33 * sizeof(u32)); | |
242 | if (!ret && count > 0) { | |
243 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
244 | &target->thread.fsr, | |
245 | 33 * sizeof(u32), | |
246 | 34 * sizeof(u32)); | |
247 | } | |
248 | ||
249 | if (!ret) | |
250 | ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, | |
251 | 34 * sizeof(u32), -1); | |
252 | return ret; | |
253 | } | |
254 | ||
255 | static const struct user_regset sparc32_regsets[] = { | |
256 | /* Format is: | |
257 | * G0 --> G7 | |
258 | * O0 --> O7 | |
259 | * L0 --> L7 | |
260 | * I0 --> I7 | |
261 | * PSR, PC, nPC, Y, WIM, TBR | |
262 | */ | |
263 | [REGSET_GENERAL] = { | |
264 | .core_note_type = NT_PRSTATUS, | |
7d4ee289 | 265 | .n = 38, |
8e3fe806 DM |
266 | .size = sizeof(u32), .align = sizeof(u32), |
267 | .get = genregs32_get, .set = genregs32_set | |
268 | }, | |
269 | /* Format is: | |
270 | * F0 --> F31 | |
271 | * empty 32-bit word | |
272 | * FSR (32--bit word) | |
273 | * FPU QUEUE COUNT (8-bit char) | |
274 | * FPU QUEUE ENTRYSIZE (8-bit char) | |
275 | * FPU ENABLED (8-bit char) | |
276 | * empty 8-bit char | |
277 | * FPU QUEUE (64 32-bit ints) | |
278 | */ | |
279 | [REGSET_FP] = { | |
280 | .core_note_type = NT_PRFPREG, | |
7d4ee289 | 281 | .n = 99, |
8e3fe806 DM |
282 | .size = sizeof(u32), .align = sizeof(u32), |
283 | .get = fpregs32_get, .set = fpregs32_set | |
284 | }, | |
285 | }; | |
286 | ||
287 | static const struct user_regset_view user_sparc32_view = { | |
288 | .name = "sparc", .e_machine = EM_SPARC, | |
289 | .regsets = sparc32_regsets, .n = ARRAY_SIZE(sparc32_regsets) | |
290 | }; | |
291 | ||
292 | const struct user_regset_view *task_user_regset_view(struct task_struct *task) | |
293 | { | |
294 | return &user_sparc32_view; | |
295 | } | |
296 | ||
a9384e23 NK |
297 | struct fps { |
298 | unsigned long regs[32]; | |
299 | unsigned long fsr; | |
300 | unsigned long flags; | |
301 | unsigned long extra; | |
302 | unsigned long fpqd; | |
303 | struct fq { | |
304 | unsigned long *insnaddr; | |
305 | unsigned long insn; | |
306 | } fpq[16]; | |
307 | }; | |
308 | ||
9b05a69e NK |
309 | long arch_ptrace(struct task_struct *child, long request, |
310 | unsigned long addr, unsigned long data) | |
1da177e4 | 311 | { |
9775369e | 312 | unsigned long addr2 = current->thread.kregs->u_regs[UREG_I4]; |
a9384e23 | 313 | void __user *addr2p; |
d256eb8d | 314 | const struct user_regset_view *view; |
a9384e23 NK |
315 | struct pt_regs __user *pregs; |
316 | struct fps __user *fps; | |
d256eb8d DM |
317 | int ret; |
318 | ||
d786a4a6 | 319 | view = task_user_regset_view(current); |
a9384e23 NK |
320 | addr2p = (void __user *) addr2; |
321 | pregs = (struct pt_regs __user *) addr; | |
322 | fps = (struct fps __user *) addr; | |
1da177e4 LT |
323 | |
324 | switch(request) { | |
1da177e4 | 325 | case PTRACE_GETREGS: { |
d256eb8d DM |
326 | ret = copy_regset_to_user(child, view, REGSET_GENERAL, |
327 | 32 * sizeof(u32), | |
328 | 4 * sizeof(u32), | |
329 | &pregs->psr); | |
330 | if (!ret) | |
331 | copy_regset_to_user(child, view, REGSET_GENERAL, | |
332 | 1 * sizeof(u32), | |
333 | 15 * sizeof(u32), | |
334 | &pregs->u_regs[0]); | |
9775369e | 335 | break; |
1da177e4 LT |
336 | } |
337 | ||
338 | case PTRACE_SETREGS: { | |
d256eb8d DM |
339 | ret = copy_regset_from_user(child, view, REGSET_GENERAL, |
340 | 32 * sizeof(u32), | |
341 | 4 * sizeof(u32), | |
342 | &pregs->psr); | |
343 | if (!ret) | |
344 | copy_regset_from_user(child, view, REGSET_GENERAL, | |
345 | 1 * sizeof(u32), | |
346 | 15 * sizeof(u32), | |
347 | &pregs->u_regs[0]); | |
9775369e | 348 | break; |
1da177e4 LT |
349 | } |
350 | ||
351 | case PTRACE_GETFPREGS: { | |
d256eb8d DM |
352 | ret = copy_regset_to_user(child, view, REGSET_FP, |
353 | 0 * sizeof(u32), | |
354 | 32 * sizeof(u32), | |
355 | &fps->regs[0]); | |
356 | if (!ret) | |
357 | ret = copy_regset_to_user(child, view, REGSET_FP, | |
358 | 33 * sizeof(u32), | |
359 | 1 * sizeof(u32), | |
360 | &fps->fsr); | |
361 | ||
362 | if (!ret) { | |
363 | if (__put_user(0, &fps->fpqd) || | |
364 | __put_user(0, &fps->flags) || | |
365 | __put_user(0, &fps->extra) || | |
366 | clear_user(fps->fpq, sizeof(fps->fpq))) | |
367 | ret = -EFAULT; | |
1da177e4 | 368 | } |
9775369e | 369 | break; |
1da177e4 LT |
370 | } |
371 | ||
372 | case PTRACE_SETFPREGS: { | |
d256eb8d DM |
373 | ret = copy_regset_from_user(child, view, REGSET_FP, |
374 | 0 * sizeof(u32), | |
375 | 32 * sizeof(u32), | |
376 | &fps->regs[0]); | |
377 | if (!ret) | |
378 | ret = copy_regset_from_user(child, view, REGSET_FP, | |
379 | 33 * sizeof(u32), | |
380 | 1 * sizeof(u32), | |
381 | &fps->fsr); | |
9775369e | 382 | break; |
1da177e4 LT |
383 | } |
384 | ||
385 | case PTRACE_READTEXT: | |
9775369e | 386 | case PTRACE_READDATA: |
a9384e23 | 387 | ret = ptrace_readdata(child, addr, addr2p, data); |
9775369e DM |
388 | |
389 | if (ret == data) | |
390 | ret = 0; | |
391 | else if (ret >= 0) | |
392 | ret = -EIO; | |
393 | break; | |
1da177e4 LT |
394 | |
395 | case PTRACE_WRITETEXT: | |
9775369e | 396 | case PTRACE_WRITEDATA: |
a9384e23 | 397 | ret = ptrace_writedata(child, addr2p, addr, data); |
9775369e DM |
398 | |
399 | if (ret == data) | |
400 | ret = 0; | |
401 | else if (ret >= 0) | |
402 | ret = -EIO; | |
403 | break; | |
1da177e4 | 404 | |
9775369e | 405 | default: |
986bef85 DM |
406 | if (request == PTRACE_SPARC_DETACH) |
407 | request = PTRACE_DETACH; | |
9775369e DM |
408 | ret = ptrace_request(child, request, addr, data); |
409 | break; | |
1da177e4 LT |
410 | } |
411 | ||
9775369e | 412 | return ret; |
1da177e4 LT |
413 | } |
414 | ||
1c133b4b | 415 | asmlinkage int syscall_trace(struct pt_regs *regs, int syscall_exit_p) |
1da177e4 | 416 | { |
1c133b4b DM |
417 | int ret = 0; |
418 | ||
419 | if (test_thread_flag(TIF_SYSCALL_TRACE)) { | |
420 | if (syscall_exit_p) | |
421 | tracehook_report_syscall_exit(regs, 0); | |
422 | else | |
423 | ret = tracehook_report_syscall_entry(regs); | |
1da177e4 | 424 | } |
1c133b4b DM |
425 | |
426 | return ret; | |
1da177e4 | 427 | } |