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