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