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 | |
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 | ||
a9384e23 NK |
326 | struct fps { |
327 | unsigned long regs[32]; | |
328 | unsigned long fsr; | |
329 | unsigned long flags; | |
330 | unsigned long extra; | |
331 | unsigned long fpqd; | |
332 | struct fq { | |
333 | unsigned long *insnaddr; | |
334 | unsigned long insn; | |
335 | } fpq[16]; | |
336 | }; | |
337 | ||
9b05a69e NK |
338 | long arch_ptrace(struct task_struct *child, long request, |
339 | unsigned long addr, unsigned long data) | |
1da177e4 | 340 | { |
9775369e | 341 | unsigned long addr2 = current->thread.kregs->u_regs[UREG_I4]; |
a9384e23 | 342 | void __user *addr2p; |
d256eb8d | 343 | const struct user_regset_view *view; |
a9384e23 NK |
344 | struct pt_regs __user *pregs; |
345 | struct fps __user *fps; | |
d256eb8d DM |
346 | int ret; |
347 | ||
d786a4a6 | 348 | view = task_user_regset_view(current); |
a9384e23 NK |
349 | addr2p = (void __user *) addr2; |
350 | pregs = (struct pt_regs __user *) addr; | |
351 | fps = (struct fps __user *) addr; | |
1da177e4 LT |
352 | |
353 | switch(request) { | |
1da177e4 | 354 | case PTRACE_GETREGS: { |
d256eb8d DM |
355 | ret = copy_regset_to_user(child, view, REGSET_GENERAL, |
356 | 32 * sizeof(u32), | |
357 | 4 * sizeof(u32), | |
358 | &pregs->psr); | |
359 | if (!ret) | |
360 | copy_regset_to_user(child, view, REGSET_GENERAL, | |
361 | 1 * sizeof(u32), | |
362 | 15 * sizeof(u32), | |
363 | &pregs->u_regs[0]); | |
9775369e | 364 | break; |
1da177e4 LT |
365 | } |
366 | ||
367 | case PTRACE_SETREGS: { | |
d256eb8d DM |
368 | ret = copy_regset_from_user(child, view, REGSET_GENERAL, |
369 | 32 * sizeof(u32), | |
370 | 4 * sizeof(u32), | |
371 | &pregs->psr); | |
372 | if (!ret) | |
373 | copy_regset_from_user(child, view, REGSET_GENERAL, | |
374 | 1 * sizeof(u32), | |
375 | 15 * sizeof(u32), | |
376 | &pregs->u_regs[0]); | |
9775369e | 377 | break; |
1da177e4 LT |
378 | } |
379 | ||
380 | case PTRACE_GETFPREGS: { | |
d256eb8d DM |
381 | ret = copy_regset_to_user(child, view, REGSET_FP, |
382 | 0 * sizeof(u32), | |
383 | 32 * sizeof(u32), | |
384 | &fps->regs[0]); | |
385 | if (!ret) | |
386 | ret = copy_regset_to_user(child, view, REGSET_FP, | |
387 | 33 * sizeof(u32), | |
388 | 1 * sizeof(u32), | |
389 | &fps->fsr); | |
390 | ||
391 | if (!ret) { | |
392 | if (__put_user(0, &fps->fpqd) || | |
393 | __put_user(0, &fps->flags) || | |
394 | __put_user(0, &fps->extra) || | |
395 | clear_user(fps->fpq, sizeof(fps->fpq))) | |
396 | ret = -EFAULT; | |
1da177e4 | 397 | } |
9775369e | 398 | break; |
1da177e4 LT |
399 | } |
400 | ||
401 | case PTRACE_SETFPREGS: { | |
d256eb8d DM |
402 | ret = copy_regset_from_user(child, view, REGSET_FP, |
403 | 0 * sizeof(u32), | |
404 | 32 * sizeof(u32), | |
405 | &fps->regs[0]); | |
406 | if (!ret) | |
407 | ret = copy_regset_from_user(child, view, REGSET_FP, | |
408 | 33 * sizeof(u32), | |
409 | 1 * sizeof(u32), | |
410 | &fps->fsr); | |
9775369e | 411 | break; |
1da177e4 LT |
412 | } |
413 | ||
414 | case PTRACE_READTEXT: | |
9775369e | 415 | case PTRACE_READDATA: |
a9384e23 | 416 | ret = ptrace_readdata(child, addr, addr2p, data); |
9775369e DM |
417 | |
418 | if (ret == data) | |
419 | ret = 0; | |
420 | else if (ret >= 0) | |
421 | ret = -EIO; | |
422 | break; | |
1da177e4 LT |
423 | |
424 | case PTRACE_WRITETEXT: | |
9775369e | 425 | case PTRACE_WRITEDATA: |
a9384e23 | 426 | ret = ptrace_writedata(child, addr2p, addr, data); |
9775369e DM |
427 | |
428 | if (ret == data) | |
429 | ret = 0; | |
430 | else if (ret >= 0) | |
431 | ret = -EIO; | |
432 | break; | |
1da177e4 | 433 | |
9775369e | 434 | default: |
986bef85 DM |
435 | if (request == PTRACE_SPARC_DETACH) |
436 | request = PTRACE_DETACH; | |
9775369e DM |
437 | ret = ptrace_request(child, request, addr, data); |
438 | break; | |
1da177e4 LT |
439 | } |
440 | ||
9775369e | 441 | return ret; |
1da177e4 LT |
442 | } |
443 | ||
1c133b4b | 444 | asmlinkage int syscall_trace(struct pt_regs *regs, int syscall_exit_p) |
1da177e4 | 445 | { |
1c133b4b DM |
446 | int ret = 0; |
447 | ||
448 | if (test_thread_flag(TIF_SYSCALL_TRACE)) { | |
449 | if (syscall_exit_p) | |
450 | tracehook_report_syscall_exit(regs, 0); | |
451 | else | |
452 | ret = tracehook_report_syscall_entry(regs); | |
1da177e4 | 453 | } |
1c133b4b DM |
454 | |
455 | return ret; | |
1da177e4 | 456 | } |