diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/parisc/kernel/ptrace.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/parisc/kernel/ptrace.c')
-rw-r--r-- | arch/parisc/kernel/ptrace.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/arch/parisc/kernel/ptrace.c b/arch/parisc/kernel/ptrace.c new file mode 100644 index 000000000000..2937a9236384 --- /dev/null +++ b/arch/parisc/kernel/ptrace.c | |||
@@ -0,0 +1,423 @@ | |||
1 | /* | ||
2 | * Kernel support for the ptrace() and syscall tracing interfaces. | ||
3 | * | ||
4 | * Copyright (C) 2000 Hewlett-Packard Co, Linuxcare Inc. | ||
5 | * Copyright (C) 2000 Matthew Wilcox <matthew@wil.cx> | ||
6 | * Copyright (C) 2000 David Huggins-Daines <dhd@debian.org> | ||
7 | */ | ||
8 | |||
9 | #include <linux/kernel.h> | ||
10 | #include <linux/sched.h> | ||
11 | #include <linux/mm.h> | ||
12 | #include <linux/smp.h> | ||
13 | #include <linux/smp_lock.h> | ||
14 | #include <linux/errno.h> | ||
15 | #include <linux/ptrace.h> | ||
16 | #include <linux/user.h> | ||
17 | #include <linux/personality.h> | ||
18 | #include <linux/security.h> | ||
19 | #include <linux/compat.h> | ||
20 | |||
21 | #include <asm/uaccess.h> | ||
22 | #include <asm/pgtable.h> | ||
23 | #include <asm/system.h> | ||
24 | #include <asm/processor.h> | ||
25 | #include <asm/offsets.h> | ||
26 | |||
27 | /* PSW bits we allow the debugger to modify */ | ||
28 | #define USER_PSW_BITS (PSW_N | PSW_V | PSW_CB) | ||
29 | |||
30 | #undef DEBUG_PTRACE | ||
31 | |||
32 | #ifdef DEBUG_PTRACE | ||
33 | #define DBG(x...) printk(x) | ||
34 | #else | ||
35 | #define DBG(x...) | ||
36 | #endif | ||
37 | |||
38 | #ifdef __LP64__ | ||
39 | |||
40 | /* This function is needed to translate 32 bit pt_regs offsets in to | ||
41 | * 64 bit pt_regs offsets. For example, a 32 bit gdb under a 64 bit kernel | ||
42 | * will request offset 12 if it wants gr3, but the lower 32 bits of | ||
43 | * the 64 bit kernels view of gr3 will be at offset 28 (3*8 + 4). | ||
44 | * This code relies on a 32 bit pt_regs being comprised of 32 bit values | ||
45 | * except for the fp registers which (a) are 64 bits, and (b) follow | ||
46 | * the gr registers at the start of pt_regs. The 32 bit pt_regs should | ||
47 | * be half the size of the 64 bit pt_regs, plus 32*4 to allow for fr[] | ||
48 | * being 64 bit in both cases. | ||
49 | */ | ||
50 | |||
51 | static long translate_usr_offset(long offset) | ||
52 | { | ||
53 | if (offset < 0) | ||
54 | return -1; | ||
55 | else if (offset <= 32*4) /* gr[0..31] */ | ||
56 | return offset * 2 + 4; | ||
57 | else if (offset <= 32*4+32*8) /* gr[0..31] + fr[0..31] */ | ||
58 | return offset + 32*4; | ||
59 | else if (offset < sizeof(struct pt_regs)/2 + 32*4) | ||
60 | return offset * 2 + 4 - 32*8; | ||
61 | else | ||
62 | return -1; | ||
63 | } | ||
64 | #endif | ||
65 | |||
66 | /* | ||
67 | * Called by kernel/ptrace.c when detaching.. | ||
68 | * | ||
69 | * Make sure single step bits etc are not set. | ||
70 | */ | ||
71 | void ptrace_disable(struct task_struct *child) | ||
72 | { | ||
73 | /* make sure the trap bits are not set */ | ||
74 | pa_psw(child)->r = 0; | ||
75 | pa_psw(child)->t = 0; | ||
76 | pa_psw(child)->h = 0; | ||
77 | pa_psw(child)->l = 0; | ||
78 | } | ||
79 | |||
80 | long sys_ptrace(long request, pid_t pid, long addr, long data) | ||
81 | { | ||
82 | struct task_struct *child; | ||
83 | long ret; | ||
84 | #ifdef DEBUG_PTRACE | ||
85 | long oaddr=addr, odata=data; | ||
86 | #endif | ||
87 | |||
88 | lock_kernel(); | ||
89 | ret = -EPERM; | ||
90 | if (request == PTRACE_TRACEME) { | ||
91 | /* are we already being traced? */ | ||
92 | if (current->ptrace & PT_PTRACED) | ||
93 | goto out; | ||
94 | |||
95 | ret = security_ptrace(current->parent, current); | ||
96 | if (ret) | ||
97 | goto out; | ||
98 | |||
99 | /* set the ptrace bit in the process flags. */ | ||
100 | current->ptrace |= PT_PTRACED; | ||
101 | ret = 0; | ||
102 | goto out; | ||
103 | } | ||
104 | |||
105 | ret = -ESRCH; | ||
106 | read_lock(&tasklist_lock); | ||
107 | child = find_task_by_pid(pid); | ||
108 | if (child) | ||
109 | get_task_struct(child); | ||
110 | read_unlock(&tasklist_lock); | ||
111 | if (!child) | ||
112 | goto out; | ||
113 | ret = -EPERM; | ||
114 | if (pid == 1) /* no messing around with init! */ | ||
115 | goto out_tsk; | ||
116 | |||
117 | if (request == PTRACE_ATTACH) { | ||
118 | ret = ptrace_attach(child); | ||
119 | goto out_tsk; | ||
120 | } | ||
121 | |||
122 | ret = ptrace_check_attach(child, request == PTRACE_KILL); | ||
123 | if (ret < 0) | ||
124 | goto out_tsk; | ||
125 | |||
126 | switch (request) { | ||
127 | case PTRACE_PEEKTEXT: /* read word at location addr. */ | ||
128 | case PTRACE_PEEKDATA: { | ||
129 | int copied; | ||
130 | |||
131 | #ifdef __LP64__ | ||
132 | if (is_compat_task(child)) { | ||
133 | unsigned int tmp; | ||
134 | |||
135 | addr &= 0xffffffffL; | ||
136 | copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); | ||
137 | ret = -EIO; | ||
138 | if (copied != sizeof(tmp)) | ||
139 | goto out_tsk; | ||
140 | ret = put_user(tmp,(unsigned int *) data); | ||
141 | DBG("sys_ptrace(PEEK%s, %d, %lx, %lx) returning %ld, data %x\n", | ||
142 | request == PTRACE_PEEKTEXT ? "TEXT" : "DATA", | ||
143 | pid, oaddr, odata, ret, tmp); | ||
144 | } | ||
145 | else | ||
146 | #endif | ||
147 | { | ||
148 | unsigned long tmp; | ||
149 | |||
150 | copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); | ||
151 | ret = -EIO; | ||
152 | if (copied != sizeof(tmp)) | ||
153 | goto out_tsk; | ||
154 | ret = put_user(tmp,(unsigned long *) data); | ||
155 | } | ||
156 | goto out_tsk; | ||
157 | } | ||
158 | |||
159 | /* when I and D space are separate, this will have to be fixed. */ | ||
160 | case PTRACE_POKETEXT: /* write the word at location addr. */ | ||
161 | case PTRACE_POKEDATA: | ||
162 | ret = 0; | ||
163 | #ifdef __LP64__ | ||
164 | if (is_compat_task(child)) { | ||
165 | unsigned int tmp = (unsigned int)data; | ||
166 | DBG("sys_ptrace(POKE%s, %d, %lx, %lx)\n", | ||
167 | request == PTRACE_POKETEXT ? "TEXT" : "DATA", | ||
168 | pid, oaddr, odata); | ||
169 | addr &= 0xffffffffL; | ||
170 | if (access_process_vm(child, addr, &tmp, sizeof(tmp), 1) == sizeof(tmp)) | ||
171 | goto out_tsk; | ||
172 | } | ||
173 | else | ||
174 | #endif | ||
175 | { | ||
176 | if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) | ||
177 | goto out_tsk; | ||
178 | } | ||
179 | ret = -EIO; | ||
180 | goto out_tsk; | ||
181 | |||
182 | /* Read the word at location addr in the USER area. For ptraced | ||
183 | processes, the kernel saves all regs on a syscall. */ | ||
184 | case PTRACE_PEEKUSR: { | ||
185 | ret = -EIO; | ||
186 | #ifdef __LP64__ | ||
187 | if (is_compat_task(child)) { | ||
188 | unsigned int tmp; | ||
189 | |||
190 | if (addr & (sizeof(int)-1)) | ||
191 | goto out_tsk; | ||
192 | if ((addr = translate_usr_offset(addr)) < 0) | ||
193 | goto out_tsk; | ||
194 | |||
195 | tmp = *(unsigned int *) ((char *) task_regs(child) + addr); | ||
196 | ret = put_user(tmp, (unsigned int *) data); | ||
197 | DBG("sys_ptrace(PEEKUSR, %d, %lx, %lx) returning %ld, addr %lx, data %x\n", | ||
198 | pid, oaddr, odata, ret, addr, tmp); | ||
199 | } | ||
200 | else | ||
201 | #endif | ||
202 | { | ||
203 | unsigned long tmp; | ||
204 | |||
205 | if ((addr & (sizeof(long)-1)) || (unsigned long) addr >= sizeof(struct pt_regs)) | ||
206 | goto out_tsk; | ||
207 | tmp = *(unsigned long *) ((char *) task_regs(child) + addr); | ||
208 | ret = put_user(tmp, (unsigned long *) data); | ||
209 | } | ||
210 | goto out_tsk; | ||
211 | } | ||
212 | |||
213 | /* Write the word at location addr in the USER area. This will need | ||
214 | to change when the kernel no longer saves all regs on a syscall. | ||
215 | FIXME. There is a problem at the moment in that r3-r18 are only | ||
216 | saved if the process is ptraced on syscall entry, and even then | ||
217 | those values are overwritten by actual register values on syscall | ||
218 | exit. */ | ||
219 | case PTRACE_POKEUSR: | ||
220 | ret = -EIO; | ||
221 | /* Some register values written here may be ignored in | ||
222 | * entry.S:syscall_restore_rfi; e.g. iaoq is written with | ||
223 | * r31/r31+4, and not with the values in pt_regs. | ||
224 | */ | ||
225 | /* PT_PSW=0, so this is valid for 32 bit processes under 64 | ||
226 | * bit kernels. | ||
227 | */ | ||
228 | if (addr == PT_PSW) { | ||
229 | /* PT_PSW=0, so this is valid for 32 bit processes | ||
230 | * under 64 bit kernels. | ||
231 | * | ||
232 | * Allow writing to Nullify, Divide-step-correction, | ||
233 | * and carry/borrow bits. | ||
234 | * BEWARE, if you set N, and then single step, it won't | ||
235 | * stop on the nullified instruction. | ||
236 | */ | ||
237 | DBG("sys_ptrace(POKEUSR, %d, %lx, %lx)\n", | ||
238 | pid, oaddr, odata); | ||
239 | data &= USER_PSW_BITS; | ||
240 | task_regs(child)->gr[0] &= ~USER_PSW_BITS; | ||
241 | task_regs(child)->gr[0] |= data; | ||
242 | ret = 0; | ||
243 | goto out_tsk; | ||
244 | } | ||
245 | #ifdef __LP64__ | ||
246 | if (is_compat_task(child)) { | ||
247 | if (addr & (sizeof(int)-1)) | ||
248 | goto out_tsk; | ||
249 | if ((addr = translate_usr_offset(addr)) < 0) | ||
250 | goto out_tsk; | ||
251 | DBG("sys_ptrace(POKEUSR, %d, %lx, %lx) addr %lx\n", | ||
252 | pid, oaddr, odata, addr); | ||
253 | if (addr >= PT_FR0 && addr <= PT_FR31 + 4) { | ||
254 | /* Special case, fp regs are 64 bits anyway */ | ||
255 | *(unsigned int *) ((char *) task_regs(child) + addr) = data; | ||
256 | ret = 0; | ||
257 | } | ||
258 | else if ((addr >= PT_GR1+4 && addr <= PT_GR31+4) || | ||
259 | addr == PT_IAOQ0+4 || addr == PT_IAOQ1+4 || | ||
260 | addr == PT_SAR+4) { | ||
261 | /* Zero the top 32 bits */ | ||
262 | *(unsigned int *) ((char *) task_regs(child) + addr - 4) = 0; | ||
263 | *(unsigned int *) ((char *) task_regs(child) + addr) = data; | ||
264 | ret = 0; | ||
265 | } | ||
266 | goto out_tsk; | ||
267 | } | ||
268 | else | ||
269 | #endif | ||
270 | { | ||
271 | if ((addr & (sizeof(long)-1)) || (unsigned long) addr >= sizeof(struct pt_regs)) | ||
272 | goto out_tsk; | ||
273 | if ((addr >= PT_GR1 && addr <= PT_GR31) || | ||
274 | addr == PT_IAOQ0 || addr == PT_IAOQ1 || | ||
275 | (addr >= PT_FR0 && addr <= PT_FR31 + 4) || | ||
276 | addr == PT_SAR) { | ||
277 | *(unsigned long *) ((char *) task_regs(child) + addr) = data; | ||
278 | ret = 0; | ||
279 | } | ||
280 | goto out_tsk; | ||
281 | } | ||
282 | |||
283 | case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ | ||
284 | case PTRACE_CONT: | ||
285 | ret = -EIO; | ||
286 | DBG("sys_ptrace(%s)\n", | ||
287 | request == PTRACE_SYSCALL ? "SYSCALL" : "CONT"); | ||
288 | if ((unsigned long) data > _NSIG) | ||
289 | goto out_tsk; | ||
290 | child->ptrace &= ~(PT_SINGLESTEP|PT_BLOCKSTEP); | ||
291 | if (request == PTRACE_SYSCALL) { | ||
292 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
293 | } else { | ||
294 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
295 | } | ||
296 | child->exit_code = data; | ||
297 | goto out_wake_notrap; | ||
298 | |||
299 | case PTRACE_KILL: | ||
300 | /* | ||
301 | * make the child exit. Best I can do is send it a | ||
302 | * sigkill. perhaps it should be put in the status | ||
303 | * that it wants to exit. | ||
304 | */ | ||
305 | DBG("sys_ptrace(KILL)\n"); | ||
306 | if (child->exit_state == EXIT_ZOMBIE) /* already dead */ | ||
307 | goto out_tsk; | ||
308 | child->exit_code = SIGKILL; | ||
309 | goto out_wake_notrap; | ||
310 | |||
311 | case PTRACE_SINGLEBLOCK: | ||
312 | DBG("sys_ptrace(SINGLEBLOCK)\n"); | ||
313 | ret = -EIO; | ||
314 | if ((unsigned long) data > _NSIG) | ||
315 | goto out_tsk; | ||
316 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
317 | child->ptrace &= ~PT_SINGLESTEP; | ||
318 | child->ptrace |= PT_BLOCKSTEP; | ||
319 | child->exit_code = data; | ||
320 | |||
321 | /* Enable taken branch trap. */ | ||
322 | pa_psw(child)->r = 0; | ||
323 | pa_psw(child)->t = 1; | ||
324 | pa_psw(child)->h = 0; | ||
325 | pa_psw(child)->l = 0; | ||
326 | goto out_wake; | ||
327 | |||
328 | case PTRACE_SINGLESTEP: | ||
329 | DBG("sys_ptrace(SINGLESTEP)\n"); | ||
330 | ret = -EIO; | ||
331 | if ((unsigned long) data > _NSIG) | ||
332 | goto out_tsk; | ||
333 | |||
334 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
335 | child->ptrace &= ~PT_BLOCKSTEP; | ||
336 | child->ptrace |= PT_SINGLESTEP; | ||
337 | child->exit_code = data; | ||
338 | |||
339 | if (pa_psw(child)->n) { | ||
340 | struct siginfo si; | ||
341 | |||
342 | /* Nullified, just crank over the queue. */ | ||
343 | task_regs(child)->iaoq[0] = task_regs(child)->iaoq[1]; | ||
344 | task_regs(child)->iasq[0] = task_regs(child)->iasq[1]; | ||
345 | task_regs(child)->iaoq[1] = task_regs(child)->iaoq[0] + 4; | ||
346 | pa_psw(child)->n = 0; | ||
347 | pa_psw(child)->x = 0; | ||
348 | pa_psw(child)->y = 0; | ||
349 | pa_psw(child)->z = 0; | ||
350 | pa_psw(child)->b = 0; | ||
351 | ptrace_disable(child); | ||
352 | /* Don't wake up the child, but let the | ||
353 | parent know something happened. */ | ||
354 | si.si_code = TRAP_TRACE; | ||
355 | si.si_addr = (void __user *) (task_regs(child)->iaoq[0] & ~3); | ||
356 | si.si_signo = SIGTRAP; | ||
357 | si.si_errno = 0; | ||
358 | force_sig_info(SIGTRAP, &si, child); | ||
359 | //notify_parent(child, SIGCHLD); | ||
360 | //ret = 0; | ||
361 | goto out_wake; | ||
362 | } | ||
363 | |||
364 | /* Enable recovery counter traps. The recovery counter | ||
365 | * itself will be set to zero on a task switch. If the | ||
366 | * task is suspended on a syscall then the syscall return | ||
367 | * path will overwrite the recovery counter with a suitable | ||
368 | * value such that it traps once back in user space. We | ||
369 | * disable interrupts in the childs PSW here also, to avoid | ||
370 | * interrupts while the recovery counter is decrementing. | ||
371 | */ | ||
372 | pa_psw(child)->r = 1; | ||
373 | pa_psw(child)->t = 0; | ||
374 | pa_psw(child)->h = 0; | ||
375 | pa_psw(child)->l = 0; | ||
376 | /* give it a chance to run. */ | ||
377 | goto out_wake; | ||
378 | |||
379 | case PTRACE_DETACH: | ||
380 | ret = ptrace_detach(child, data); | ||
381 | goto out_tsk; | ||
382 | |||
383 | case PTRACE_GETEVENTMSG: | ||
384 | ret = put_user(child->ptrace_message, (unsigned int __user *) data); | ||
385 | goto out_tsk; | ||
386 | |||
387 | default: | ||
388 | ret = ptrace_request(child, request, addr, data); | ||
389 | goto out_tsk; | ||
390 | } | ||
391 | |||
392 | out_wake_notrap: | ||
393 | ptrace_disable(child); | ||
394 | out_wake: | ||
395 | wake_up_process(child); | ||
396 | ret = 0; | ||
397 | out_tsk: | ||
398 | put_task_struct(child); | ||
399 | out: | ||
400 | unlock_kernel(); | ||
401 | DBG("sys_ptrace(%ld, %d, %lx, %lx) returning %ld\n", | ||
402 | request, pid, oaddr, odata, ret); | ||
403 | return ret; | ||
404 | } | ||
405 | |||
406 | void syscall_trace(void) | ||
407 | { | ||
408 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | ||
409 | return; | ||
410 | if (!(current->ptrace & PT_PTRACED)) | ||
411 | return; | ||
412 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | ||
413 | ? 0x80 : 0)); | ||
414 | /* | ||
415 | * this isn't the same as continuing with a signal, but it will do | ||
416 | * for normal use. strace only continues with a signal if the | ||
417 | * stopping signal is not SIGTRAP. -brl | ||
418 | */ | ||
419 | if (current->exit_code) { | ||
420 | send_sig(current->exit_code, current, 1); | ||
421 | current->exit_code = 0; | ||
422 | } | ||
423 | } | ||