diff options
Diffstat (limited to 'arch/cris/arch-v10/kernel/ptrace.c')
-rw-r--r-- | arch/cris/arch-v10/kernel/ptrace.c | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/arch/cris/arch-v10/kernel/ptrace.c b/arch/cris/arch-v10/kernel/ptrace.c new file mode 100644 index 000000000000..da15db8ae482 --- /dev/null +++ b/arch/cris/arch-v10/kernel/ptrace.c | |||
@@ -0,0 +1,314 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2000-2003, Axis Communications AB. | ||
3 | */ | ||
4 | |||
5 | #include <linux/kernel.h> | ||
6 | #include <linux/sched.h> | ||
7 | #include <linux/mm.h> | ||
8 | #include <linux/smp.h> | ||
9 | #include <linux/smp_lock.h> | ||
10 | #include <linux/errno.h> | ||
11 | #include <linux/ptrace.h> | ||
12 | #include <linux/user.h> | ||
13 | |||
14 | #include <asm/uaccess.h> | ||
15 | #include <asm/page.h> | ||
16 | #include <asm/pgtable.h> | ||
17 | #include <asm/system.h> | ||
18 | #include <asm/processor.h> | ||
19 | |||
20 | /* | ||
21 | * Determines which bits in DCCR the user has access to. | ||
22 | * 1 = access, 0 = no access. | ||
23 | */ | ||
24 | #define DCCR_MASK 0x0000001f /* XNZVC */ | ||
25 | |||
26 | /* | ||
27 | * Get contents of register REGNO in task TASK. | ||
28 | */ | ||
29 | inline long get_reg(struct task_struct *task, unsigned int regno) | ||
30 | { | ||
31 | /* USP is a special case, it's not in the pt_regs struct but | ||
32 | * in the tasks thread struct | ||
33 | */ | ||
34 | |||
35 | if (regno == PT_USP) | ||
36 | return task->thread.usp; | ||
37 | else if (regno < PT_MAX) | ||
38 | return ((unsigned long *)user_regs(task->thread_info))[regno]; | ||
39 | else | ||
40 | return 0; | ||
41 | } | ||
42 | |||
43 | /* | ||
44 | * Write contents of register REGNO in task TASK. | ||
45 | */ | ||
46 | inline int put_reg(struct task_struct *task, unsigned int regno, | ||
47 | unsigned long data) | ||
48 | { | ||
49 | if (regno == PT_USP) | ||
50 | task->thread.usp = data; | ||
51 | else if (regno < PT_MAX) | ||
52 | ((unsigned long *)user_regs(task->thread_info))[regno] = data; | ||
53 | else | ||
54 | return -1; | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | /* | ||
59 | * Called by kernel/ptrace.c when detaching. | ||
60 | * | ||
61 | * Make sure the single step bit is not set. | ||
62 | */ | ||
63 | void | ||
64 | ptrace_disable(struct task_struct *child) | ||
65 | { | ||
66 | /* Todo - pending singlesteps? */ | ||
67 | } | ||
68 | |||
69 | /* | ||
70 | * Note that this implementation of ptrace behaves differently from vanilla | ||
71 | * ptrace. Contrary to what the man page says, in the PTRACE_PEEKTEXT, | ||
72 | * PTRACE_PEEKDATA, and PTRACE_PEEKUSER requests the data variable is not | ||
73 | * ignored. Instead, the data variable is expected to point at a location | ||
74 | * (in user space) where the result of the ptrace call is written (instead of | ||
75 | * being returned). | ||
76 | */ | ||
77 | asmlinkage int | ||
78 | sys_ptrace(long request, long pid, long addr, long data) | ||
79 | { | ||
80 | struct task_struct *child; | ||
81 | int ret; | ||
82 | unsigned long __user *datap = (unsigned long __user *)data; | ||
83 | |||
84 | lock_kernel(); | ||
85 | ret = -EPERM; | ||
86 | |||
87 | if (request == PTRACE_TRACEME) { | ||
88 | if (current->ptrace & PT_PTRACED) | ||
89 | goto out; | ||
90 | |||
91 | current->ptrace |= PT_PTRACED; | ||
92 | ret = 0; | ||
93 | goto out; | ||
94 | } | ||
95 | |||
96 | ret = -ESRCH; | ||
97 | read_lock(&tasklist_lock); | ||
98 | child = find_task_by_pid(pid); | ||
99 | |||
100 | if (child) | ||
101 | get_task_struct(child); | ||
102 | |||
103 | read_unlock(&tasklist_lock); | ||
104 | |||
105 | if (!child) | ||
106 | goto out; | ||
107 | |||
108 | ret = -EPERM; | ||
109 | |||
110 | if (pid == 1) /* Leave the init process alone! */ | ||
111 | goto out_tsk; | ||
112 | |||
113 | if (request == PTRACE_ATTACH) { | ||
114 | ret = ptrace_attach(child); | ||
115 | goto out_tsk; | ||
116 | } | ||
117 | |||
118 | ret = ptrace_check_attach(child, request == PTRACE_KILL); | ||
119 | if (ret < 0) | ||
120 | goto out_tsk; | ||
121 | |||
122 | switch (request) { | ||
123 | /* Read word at location address. */ | ||
124 | case PTRACE_PEEKTEXT: | ||
125 | case PTRACE_PEEKDATA: { | ||
126 | unsigned long tmp; | ||
127 | int copied; | ||
128 | |||
129 | copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); | ||
130 | ret = -EIO; | ||
131 | |||
132 | if (copied != sizeof(tmp)) | ||
133 | break; | ||
134 | |||
135 | ret = put_user(tmp,datap); | ||
136 | break; | ||
137 | } | ||
138 | |||
139 | /* Read the word at location address in the USER area. */ | ||
140 | case PTRACE_PEEKUSR: { | ||
141 | unsigned long tmp; | ||
142 | |||
143 | ret = -EIO; | ||
144 | if ((addr & 3) || addr < 0 || addr > PT_MAX << 2) | ||
145 | break; | ||
146 | |||
147 | tmp = get_reg(child, addr >> 2); | ||
148 | ret = put_user(tmp, datap); | ||
149 | break; | ||
150 | } | ||
151 | |||
152 | /* Write the word at location address. */ | ||
153 | case PTRACE_POKETEXT: | ||
154 | case PTRACE_POKEDATA: | ||
155 | ret = 0; | ||
156 | |||
157 | if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) | ||
158 | break; | ||
159 | |||
160 | ret = -EIO; | ||
161 | break; | ||
162 | |||
163 | /* Write the word at location address in the USER area. */ | ||
164 | case PTRACE_POKEUSR: | ||
165 | ret = -EIO; | ||
166 | if ((addr & 3) || addr < 0 || addr > PT_MAX << 2) | ||
167 | break; | ||
168 | |||
169 | addr >>= 2; | ||
170 | |||
171 | if (addr == PT_DCCR) { | ||
172 | /* don't allow the tracing process to change stuff like | ||
173 | * interrupt enable, kernel/user bit, dma enables etc. | ||
174 | */ | ||
175 | data &= DCCR_MASK; | ||
176 | data |= get_reg(child, PT_DCCR) & ~DCCR_MASK; | ||
177 | } | ||
178 | if (put_reg(child, addr, data)) | ||
179 | break; | ||
180 | ret = 0; | ||
181 | break; | ||
182 | |||
183 | case PTRACE_SYSCALL: | ||
184 | case PTRACE_CONT: | ||
185 | ret = -EIO; | ||
186 | |||
187 | if ((unsigned long) data > _NSIG) | ||
188 | break; | ||
189 | |||
190 | if (request == PTRACE_SYSCALL) { | ||
191 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
192 | } | ||
193 | else { | ||
194 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
195 | } | ||
196 | |||
197 | child->exit_code = data; | ||
198 | |||
199 | /* TODO: make sure any pending breakpoint is killed */ | ||
200 | wake_up_process(child); | ||
201 | ret = 0; | ||
202 | |||
203 | break; | ||
204 | |||
205 | /* Make the child exit by sending it a sigkill. */ | ||
206 | case PTRACE_KILL: | ||
207 | ret = 0; | ||
208 | |||
209 | if (child->state == TASK_ZOMBIE) | ||
210 | break; | ||
211 | |||
212 | child->exit_code = SIGKILL; | ||
213 | |||
214 | /* TODO: make sure any pending breakpoint is killed */ | ||
215 | wake_up_process(child); | ||
216 | break; | ||
217 | |||
218 | /* Set the trap flag. */ | ||
219 | case PTRACE_SINGLESTEP: | ||
220 | ret = -EIO; | ||
221 | |||
222 | if ((unsigned long) data > _NSIG) | ||
223 | break; | ||
224 | |||
225 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
226 | |||
227 | /* TODO: set some clever breakpoint mechanism... */ | ||
228 | |||
229 | child->exit_code = data; | ||
230 | wake_up_process(child); | ||
231 | ret = 0; | ||
232 | break; | ||
233 | |||
234 | case PTRACE_DETACH: | ||
235 | ret = ptrace_detach(child, data); | ||
236 | break; | ||
237 | |||
238 | /* Get all GP registers from the child. */ | ||
239 | case PTRACE_GETREGS: { | ||
240 | int i; | ||
241 | unsigned long tmp; | ||
242 | |||
243 | for (i = 0; i <= PT_MAX; i++) { | ||
244 | tmp = get_reg(child, i); | ||
245 | |||
246 | if (put_user(tmp, datap)) { | ||
247 | ret = -EFAULT; | ||
248 | goto out_tsk; | ||
249 | } | ||
250 | |||
251 | data += sizeof(long); | ||
252 | } | ||
253 | |||
254 | ret = 0; | ||
255 | break; | ||
256 | } | ||
257 | |||
258 | /* Set all GP registers in the child. */ | ||
259 | case PTRACE_SETREGS: { | ||
260 | int i; | ||
261 | unsigned long tmp; | ||
262 | |||
263 | for (i = 0; i <= PT_MAX; i++) { | ||
264 | if (get_user(tmp, datap)) { | ||
265 | ret = -EFAULT; | ||
266 | goto out_tsk; | ||
267 | } | ||
268 | |||
269 | if (i == PT_DCCR) { | ||
270 | tmp &= DCCR_MASK; | ||
271 | tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK; | ||
272 | } | ||
273 | |||
274 | put_reg(child, i, tmp); | ||
275 | data += sizeof(long); | ||
276 | } | ||
277 | |||
278 | ret = 0; | ||
279 | break; | ||
280 | } | ||
281 | |||
282 | default: | ||
283 | ret = ptrace_request(child, request, addr, data); | ||
284 | break; | ||
285 | } | ||
286 | out_tsk: | ||
287 | put_task_struct(child); | ||
288 | out: | ||
289 | unlock_kernel(); | ||
290 | return ret; | ||
291 | } | ||
292 | |||
293 | void do_syscall_trace(void) | ||
294 | { | ||
295 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | ||
296 | return; | ||
297 | |||
298 | if (!(current->ptrace & PT_PTRACED)) | ||
299 | return; | ||
300 | |||
301 | /* the 0x80 provides a way for the tracing parent to distinguish | ||
302 | between a syscall stop and SIGTRAP delivery */ | ||
303 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | ||
304 | ? 0x80 : 0)); | ||
305 | |||
306 | /* | ||
307 | * This isn't the same as continuing with a signal, but it will do for | ||
308 | * normal use. | ||
309 | */ | ||
310 | if (current->exit_code) { | ||
311 | send_sig(current->exit_code, current, 1); | ||
312 | current->exit_code = 0; | ||
313 | } | ||
314 | } | ||