diff options
Diffstat (limited to 'arch/mn10300/kernel/ptrace.c')
-rw-r--r-- | arch/mn10300/kernel/ptrace.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/arch/mn10300/kernel/ptrace.c b/arch/mn10300/kernel/ptrace.c new file mode 100644 index 000000000000..d6d6cdc75c52 --- /dev/null +++ b/arch/mn10300/kernel/ptrace.c | |||
@@ -0,0 +1,379 @@ | |||
1 | /* MN10300 Process tracing | ||
2 | * | ||
3 | * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. | ||
4 | * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. | ||
5 | * Modified by David Howells (dhowells@redhat.com) | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public Licence | ||
9 | * as published by the Free Software Foundation; either version | ||
10 | * 2 of the Licence, or (at your option) any later version. | ||
11 | */ | ||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/sched.h> | ||
14 | #include <linux/mm.h> | ||
15 | #include <linux/smp.h> | ||
16 | #include <linux/smp_lock.h> | ||
17 | #include <linux/errno.h> | ||
18 | #include <linux/ptrace.h> | ||
19 | #include <linux/user.h> | ||
20 | #include <asm/uaccess.h> | ||
21 | #include <asm/pgtable.h> | ||
22 | #include <asm/system.h> | ||
23 | #include <asm/processor.h> | ||
24 | #include <asm/cacheflush.h> | ||
25 | #include <asm/fpu.h> | ||
26 | #include <asm/asm-offsets.h> | ||
27 | |||
28 | /* | ||
29 | * translate ptrace register IDs into struct pt_regs offsets | ||
30 | */ | ||
31 | static const u8 ptrace_regid_to_frame[] = { | ||
32 | [PT_A3 << 2] = REG_A3, | ||
33 | [PT_A2 << 2] = REG_A2, | ||
34 | [PT_D3 << 2] = REG_D3, | ||
35 | [PT_D2 << 2] = REG_D2, | ||
36 | [PT_MCVF << 2] = REG_MCVF, | ||
37 | [PT_MCRL << 2] = REG_MCRL, | ||
38 | [PT_MCRH << 2] = REG_MCRH, | ||
39 | [PT_MDRQ << 2] = REG_MDRQ, | ||
40 | [PT_E1 << 2] = REG_E1, | ||
41 | [PT_E0 << 2] = REG_E0, | ||
42 | [PT_E7 << 2] = REG_E7, | ||
43 | [PT_E6 << 2] = REG_E6, | ||
44 | [PT_E5 << 2] = REG_E5, | ||
45 | [PT_E4 << 2] = REG_E4, | ||
46 | [PT_E3 << 2] = REG_E3, | ||
47 | [PT_E2 << 2] = REG_E2, | ||
48 | [PT_SP << 2] = REG_SP, | ||
49 | [PT_LAR << 2] = REG_LAR, | ||
50 | [PT_LIR << 2] = REG_LIR, | ||
51 | [PT_MDR << 2] = REG_MDR, | ||
52 | [PT_A1 << 2] = REG_A1, | ||
53 | [PT_A0 << 2] = REG_A0, | ||
54 | [PT_D1 << 2] = REG_D1, | ||
55 | [PT_D0 << 2] = REG_D0, | ||
56 | [PT_ORIG_D0 << 2] = REG_ORIG_D0, | ||
57 | [PT_EPSW << 2] = REG_EPSW, | ||
58 | [PT_PC << 2] = REG_PC, | ||
59 | }; | ||
60 | |||
61 | static inline int get_stack_long(struct task_struct *task, int offset) | ||
62 | { | ||
63 | return *(unsigned long *) | ||
64 | ((unsigned long) task->thread.uregs + offset); | ||
65 | } | ||
66 | |||
67 | /* | ||
68 | * this routine will put a word on the processes privileged stack. | ||
69 | * the offset is how far from the base addr as stored in the TSS. | ||
70 | * this routine assumes that all the privileged stacks are in our | ||
71 | * data space. | ||
72 | */ | ||
73 | static inline | ||
74 | int put_stack_long(struct task_struct *task, int offset, unsigned long data) | ||
75 | { | ||
76 | unsigned long stack; | ||
77 | |||
78 | stack = (unsigned long) task->thread.uregs + offset; | ||
79 | *(unsigned long *) stack = data; | ||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | static inline unsigned long get_fpregs(struct fpu_state_struct *buf, | ||
84 | struct task_struct *tsk) | ||
85 | { | ||
86 | return __copy_to_user(buf, &tsk->thread.fpu_state, | ||
87 | sizeof(struct fpu_state_struct)); | ||
88 | } | ||
89 | |||
90 | static inline unsigned long set_fpregs(struct task_struct *tsk, | ||
91 | struct fpu_state_struct *buf) | ||
92 | { | ||
93 | return __copy_from_user(&tsk->thread.fpu_state, buf, | ||
94 | sizeof(struct fpu_state_struct)); | ||
95 | } | ||
96 | |||
97 | static inline void fpsave_init(struct task_struct *task) | ||
98 | { | ||
99 | memset(&task->thread.fpu_state, 0, sizeof(struct fpu_state_struct)); | ||
100 | } | ||
101 | |||
102 | /* | ||
103 | * make sure the single step bit is not set | ||
104 | */ | ||
105 | void ptrace_disable(struct task_struct *child) | ||
106 | { | ||
107 | #ifndef CONFIG_MN10300_USING_JTAG | ||
108 | struct user *dummy = NULL; | ||
109 | long tmp; | ||
110 | |||
111 | tmp = get_stack_long(child, (unsigned long) &dummy->regs.epsw); | ||
112 | tmp &= ~EPSW_T; | ||
113 | put_stack_long(child, (unsigned long) &dummy->regs.epsw, tmp); | ||
114 | #endif | ||
115 | } | ||
116 | |||
117 | /* | ||
118 | * set the single step bit | ||
119 | */ | ||
120 | void ptrace_enable(struct task_struct *child) | ||
121 | { | ||
122 | #ifndef CONFIG_MN10300_USING_JTAG | ||
123 | struct user *dummy = NULL; | ||
124 | long tmp; | ||
125 | |||
126 | tmp = get_stack_long(child, (unsigned long) &dummy->regs.epsw); | ||
127 | tmp |= EPSW_T; | ||
128 | put_stack_long(child, (unsigned long) &dummy->regs.epsw, tmp); | ||
129 | #endif | ||
130 | } | ||
131 | |||
132 | /* | ||
133 | * handle the arch-specific side of process tracing | ||
134 | */ | ||
135 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | ||
136 | { | ||
137 | struct fpu_state_struct fpu_state; | ||
138 | int i, ret; | ||
139 | |||
140 | switch (request) { | ||
141 | /* read the word at location addr. */ | ||
142 | case PTRACE_PEEKTEXT: { | ||
143 | unsigned long tmp; | ||
144 | int copied; | ||
145 | |||
146 | copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); | ||
147 | ret = -EIO; | ||
148 | if (copied != sizeof(tmp)) | ||
149 | break; | ||
150 | ret = put_user(tmp, (unsigned long *) data); | ||
151 | break; | ||
152 | } | ||
153 | |||
154 | /* read the word at location addr. */ | ||
155 | case PTRACE_PEEKDATA: { | ||
156 | unsigned long tmp; | ||
157 | int copied; | ||
158 | |||
159 | copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); | ||
160 | ret = -EIO; | ||
161 | if (copied != sizeof(tmp)) | ||
162 | break; | ||
163 | ret = put_user(tmp, (unsigned long *) data); | ||
164 | break; | ||
165 | } | ||
166 | |||
167 | /* read the word at location addr in the USER area. */ | ||
168 | case PTRACE_PEEKUSR: { | ||
169 | unsigned long tmp; | ||
170 | |||
171 | ret = -EIO; | ||
172 | if ((addr & 3) || addr < 0 || | ||
173 | addr > sizeof(struct user) - 3) | ||
174 | break; | ||
175 | |||
176 | tmp = 0; /* Default return condition */ | ||
177 | if (addr < NR_PTREGS << 2) | ||
178 | tmp = get_stack_long(child, | ||
179 | ptrace_regid_to_frame[addr]); | ||
180 | ret = put_user(tmp, (unsigned long *) data); | ||
181 | break; | ||
182 | } | ||
183 | |||
184 | /* write the word at location addr. */ | ||
185 | case PTRACE_POKETEXT: | ||
186 | case PTRACE_POKEDATA: | ||
187 | if (access_process_vm(child, addr, &data, sizeof(data), 1) == | ||
188 | sizeof(data)) | ||
189 | ret = 0; | ||
190 | else | ||
191 | ret = -EIO; | ||
192 | break; | ||
193 | |||
194 | /* write the word at location addr in the USER area */ | ||
195 | case PTRACE_POKEUSR: | ||
196 | ret = -EIO; | ||
197 | if ((addr & 3) || addr < 0 || | ||
198 | addr > sizeof(struct user) - 3) | ||
199 | break; | ||
200 | |||
201 | ret = 0; | ||
202 | if (addr < NR_PTREGS << 2) | ||
203 | ret = put_stack_long(child, ptrace_regid_to_frame[addr], | ||
204 | data); | ||
205 | break; | ||
206 | |||
207 | /* continue and stop at next (return from) syscall */ | ||
208 | case PTRACE_SYSCALL: | ||
209 | /* restart after signal. */ | ||
210 | case PTRACE_CONT: | ||
211 | ret = -EIO; | ||
212 | if ((unsigned long) data > _NSIG) | ||
213 | break; | ||
214 | if (request == PTRACE_SYSCALL) | ||
215 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
216 | else | ||
217 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
218 | child->exit_code = data; | ||
219 | ptrace_disable(child); | ||
220 | wake_up_process(child); | ||
221 | ret = 0; | ||
222 | break; | ||
223 | |||
224 | /* | ||
225 | * make the child exit | ||
226 | * - the best I can do is send it a sigkill | ||
227 | * - perhaps it should be put in the status that it wants to | ||
228 | * exit | ||
229 | */ | ||
230 | case PTRACE_KILL: | ||
231 | ret = 0; | ||
232 | if (child->exit_state == EXIT_ZOMBIE) /* already dead */ | ||
233 | break; | ||
234 | child->exit_code = SIGKILL; | ||
235 | clear_tsk_thread_flag(child, TIF_SINGLESTEP); | ||
236 | ptrace_disable(child); | ||
237 | wake_up_process(child); | ||
238 | break; | ||
239 | |||
240 | case PTRACE_SINGLESTEP: /* set the trap flag. */ | ||
241 | #ifndef CONFIG_MN10300_USING_JTAG | ||
242 | ret = -EIO; | ||
243 | if ((unsigned long) data > _NSIG) | ||
244 | break; | ||
245 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
246 | ptrace_enable(child); | ||
247 | child->exit_code = data; | ||
248 | wake_up_process(child); | ||
249 | ret = 0; | ||
250 | #else | ||
251 | ret = -EINVAL; | ||
252 | #endif | ||
253 | break; | ||
254 | |||
255 | case PTRACE_DETACH: /* detach a process that was attached. */ | ||
256 | ret = ptrace_detach(child, data); | ||
257 | break; | ||
258 | |||
259 | /* Get all gp regs from the child. */ | ||
260 | case PTRACE_GETREGS: { | ||
261 | unsigned long tmp; | ||
262 | |||
263 | if (!access_ok(VERIFY_WRITE, (unsigned *) data, NR_PTREGS << 2)) { | ||
264 | ret = -EIO; | ||
265 | break; | ||
266 | } | ||
267 | |||
268 | for (i = 0; i < NR_PTREGS << 2; i += 4) { | ||
269 | tmp = get_stack_long(child, ptrace_regid_to_frame[i]); | ||
270 | __put_user(tmp, (unsigned long *) data); | ||
271 | data += sizeof(tmp); | ||
272 | } | ||
273 | ret = 0; | ||
274 | break; | ||
275 | } | ||
276 | |||
277 | case PTRACE_SETREGS: { /* Set all gp regs in the child. */ | ||
278 | unsigned long tmp; | ||
279 | |||
280 | if (!access_ok(VERIFY_READ, (unsigned long *)data, | ||
281 | sizeof(struct pt_regs))) { | ||
282 | ret = -EIO; | ||
283 | break; | ||
284 | } | ||
285 | |||
286 | for (i = 0; i < NR_PTREGS << 2; i += 4) { | ||
287 | __get_user(tmp, (unsigned long *) data); | ||
288 | put_stack_long(child, ptrace_regid_to_frame[i], tmp); | ||
289 | data += sizeof(tmp); | ||
290 | } | ||
291 | ret = 0; | ||
292 | break; | ||
293 | } | ||
294 | |||
295 | case PTRACE_GETFPREGS: { /* Get the child FPU state. */ | ||
296 | if (is_using_fpu(child)) { | ||
297 | unlazy_fpu(child); | ||
298 | fpu_state = child->thread.fpu_state; | ||
299 | } else { | ||
300 | memset(&fpu_state, 0, sizeof(fpu_state)); | ||
301 | } | ||
302 | |||
303 | ret = -EIO; | ||
304 | if (copy_to_user((void *) data, &fpu_state, | ||
305 | sizeof(fpu_state)) == 0) | ||
306 | ret = 0; | ||
307 | break; | ||
308 | } | ||
309 | |||
310 | case PTRACE_SETFPREGS: { /* Set the child FPU state. */ | ||
311 | ret = -EFAULT; | ||
312 | if (copy_from_user(&fpu_state, (const void *) data, | ||
313 | sizeof(fpu_state)) == 0) { | ||
314 | fpu_kill_state(child); | ||
315 | child->thread.fpu_state = fpu_state; | ||
316 | set_using_fpu(child); | ||
317 | ret = 0; | ||
318 | } | ||
319 | break; | ||
320 | } | ||
321 | |||
322 | case PTRACE_SETOPTIONS: { | ||
323 | if (data & PTRACE_O_TRACESYSGOOD) | ||
324 | child->ptrace |= PT_TRACESYSGOOD; | ||
325 | else | ||
326 | child->ptrace &= ~PT_TRACESYSGOOD; | ||
327 | ret = 0; | ||
328 | break; | ||
329 | } | ||
330 | |||
331 | default: | ||
332 | ret = -EIO; | ||
333 | break; | ||
334 | } | ||
335 | |||
336 | return ret; | ||
337 | } | ||
338 | |||
339 | /* | ||
340 | * notification of system call entry/exit | ||
341 | * - triggered by current->work.syscall_trace | ||
342 | */ | ||
343 | asmlinkage void do_syscall_trace(struct pt_regs *regs, int entryexit) | ||
344 | { | ||
345 | #if 0 | ||
346 | /* just in case... */ | ||
347 | printk(KERN_DEBUG "[%d] syscall_%lu(%lx,%lx,%lx,%lx) = %lx\n", | ||
348 | current->pid, | ||
349 | regs->orig_d0, | ||
350 | regs->a0, | ||
351 | regs->d1, | ||
352 | regs->a3, | ||
353 | regs->a2, | ||
354 | regs->d0); | ||
355 | return; | ||
356 | #endif | ||
357 | |||
358 | if (!test_thread_flag(TIF_SYSCALL_TRACE) && | ||
359 | !test_thread_flag(TIF_SINGLESTEP)) | ||
360 | return; | ||
361 | if (!(current->ptrace & PT_PTRACED)) | ||
362 | return; | ||
363 | |||
364 | /* the 0x80 provides a way for the tracing parent to distinguish | ||
365 | between a syscall stop and SIGTRAP delivery */ | ||
366 | ptrace_notify(SIGTRAP | | ||
367 | ((current->ptrace & PT_TRACESYSGOOD) && | ||
368 | !test_thread_flag(TIF_SINGLESTEP) ? 0x80 : 0)); | ||
369 | |||
370 | /* | ||
371 | * this isn't the same as continuing with a signal, but it will do | ||
372 | * for normal use. strace only continues with a signal if the | ||
373 | * stopping signal is not SIGTRAP. -brl | ||
374 | */ | ||
375 | if (current->exit_code) { | ||
376 | send_sig(current->exit_code, current, 1); | ||
377 | current->exit_code = 0; | ||
378 | } | ||
379 | } | ||