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