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