more changes on original files
[linux-2.4.git] / arch / cris / kernel / ptrace.c
1 /*
2  *  linux/arch/cris/kernel/ptrace.c
3  *
4  * Parts taken from the m68k port.
5  * 
6  * Copyright (c) 2000, 2001 Axis Communications AB
7  *
8  * Authors:   Bjorn Wesen
9  *
10  * $Log: ptrace.c,v $
11  * Revision 1.9  2003/10/01 11:34:23  aurer
12  * * Allow PTRACE_PEEKUSR and PTRACE_POKEUSR to access USP.
13  * * Removed nonsensical comment about ptrace behavior.
14  *
15  * Revision 1.8  2001/11/12 18:26:21  pkj
16  * Fixed compiler warnings.
17  *
18  * Revision 1.7  2001/09/26 11:53:49  bjornw
19  * PTRACE_DETACH works more simple in 2.4.10
20  *
21  * Revision 1.6  2001/07/25 16:08:47  bjornw
22  * PTRACE_ATTACH bulk moved into arch-independant code in 2.4.7
23  *
24  * Revision 1.5  2001/03/26 14:24:28  orjanf
25  * * Changed loop condition.
26  * * Added comment documenting non-standard ptrace behaviour.
27  *
28  * Revision 1.4  2001/03/20 19:44:41  bjornw
29  * Use the user_regs macro instead of thread.esp0
30  *
31  * Revision 1.3  2000/12/18 23:45:25  bjornw
32  * Linux/CRIS first version
33  *
34  *
35  */
36
37 #include <linux/kernel.h>
38 #include <linux/sched.h>
39 #include <linux/mm.h>
40 #include <linux/smp.h>
41 #include <linux/smp_lock.h>
42 #include <linux/errno.h>
43 #include <linux/ptrace.h>
44 #include <linux/user.h>
45
46 #include <asm/uaccess.h>
47 #include <asm/page.h>
48 #include <asm/pgtable.h>
49 #include <asm/system.h>
50 #include <asm/processor.h>
51
52 /*
53  * does not yet catch signals sent when the child dies.
54  * in exit.c or in signal.c.
55  */
56
57 /* determines which bits in DCCR the user has access to. */
58 /* 1 = access 0 = no access */
59 #define DCCR_MASK 0x0000001f     /* XNZVC */
60
61 /*
62  * Get contents of register REGNO in task TASK.
63  */
64 static inline long get_reg(struct task_struct *task, unsigned int regno)
65 {
66         /* USP is a special case, it's not in the pt_regs struct but
67          * in the tasks thread struct
68          */
69
70         if (regno == PT_USP)
71                 return task->thread.usp;
72         else if (regno < PT_MAX)
73                 return ((unsigned long *)user_regs(task))[regno];
74         else
75                 return 0;
76 }
77
78 /*
79  * Write contents of register REGNO in task TASK.
80  */
81 static inline int put_reg(struct task_struct *task, unsigned int regno,
82                           unsigned long data)
83 {
84         if (regno == PT_USP)
85                 task->thread.usp = data;
86         else if (regno < PT_MAX)
87                 ((unsigned long *)user_regs(task))[regno] = data;
88         else
89                 return -1;
90         return 0;
91 }
92
93 /*
94  * Called by kernel/ptrace.c when detaching..
95  *
96  * Make sure the single step bit is not set.
97  */
98 void ptrace_disable(struct task_struct *child)
99 {
100        /* Todo - pending singlesteps? */
101 }
102
103 asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
104 {
105         struct task_struct *child;
106         int ret;
107
108         lock_kernel();
109         ret = -EPERM;
110         if (request == PTRACE_TRACEME) {
111                 /* are we already being traced? */
112                 if (current->ptrace & PT_PTRACED)
113                         goto out;
114                 /* set the ptrace bit in the process flags. */
115                 current->ptrace |= PT_PTRACED;
116                 ret = 0;
117                 goto out;
118         }
119         ret = -ESRCH;
120         read_lock(&tasklist_lock);
121         child = find_task_by_pid(pid);
122         if (child)
123                 get_task_struct(child);
124         read_unlock(&tasklist_lock);
125         if (!child)
126                 goto out;
127         ret = -EPERM;
128         if (pid == 1)           /* you may not mess with init */
129                 goto out_tsk;
130         if (request == PTRACE_ATTACH) {
131                 ret = ptrace_attach(child);
132                 goto out_tsk;
133         }
134         ret = -ESRCH;
135         if (!(child->ptrace & PT_PTRACED))
136                 goto out_tsk;
137         if (child->state != TASK_STOPPED) {
138                 if (request != PTRACE_KILL)
139                         goto out_tsk;
140         }
141         if (child->p_pptr != current)
142                 goto out_tsk;
143
144         switch (request) {
145         /* when I and D space are separate, these will need to be fixed. */
146                 case PTRACE_PEEKTEXT: /* read word at location addr. */ 
147                 case PTRACE_PEEKDATA: {
148                         unsigned long tmp;
149                         int copied;
150
151                         copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
152                         ret = -EIO;
153                         if (copied != sizeof(tmp))
154                                 break;
155                         ret = put_user(tmp,(unsigned long *) data);
156                         break;
157                 }
158
159                 /* read the word at location addr in the USER area. */
160                 case PTRACE_PEEKUSR: {
161                         unsigned long tmp;
162
163                         ret = -EIO;
164                         if ((addr & 3) || addr < 0 || addr > PT_MAX << 2)
165                                 break;
166
167                         tmp = get_reg(child, addr >> 2);
168                         ret = put_user(tmp, (unsigned long *)data);
169                         break;
170                 }
171
172                 /* when I and D space are separate, this will have to be fixed. */
173                 case PTRACE_POKETEXT: /* write the word at location addr. */
174                 case PTRACE_POKEDATA:
175                         ret = 0;
176                         if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data))
177                                 break;
178                         ret = -EIO;
179                         break;
180
181                 case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
182                         ret = -EIO;
183                         if ((addr & 3) || addr < 0 || addr > PT_MAX << 2)
184                                 break;
185
186                         addr >>= 2;
187
188                         if (addr == PT_DCCR) {
189                                 /* don't allow the tracing process to change stuff like
190                                  * interrupt enable, kernel/user bit, dma enables etc.
191                                  */
192                                 data &= DCCR_MASK;
193                                 data |= get_reg(child, PT_DCCR) & ~DCCR_MASK;
194                         }
195                         if (put_reg(child, addr, data))
196                                 break;
197                         ret = 0;
198                         break;
199
200                 case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */
201                 case PTRACE_CONT: /* restart after signal. */
202                         ret = -EIO;
203                         if ((unsigned long) data > _NSIG)
204                                 break;
205                         if (request == PTRACE_SYSCALL)
206                                 child->ptrace |= PT_TRACESYS;
207                         else
208                                 child->ptrace &= ~PT_TRACESYS;
209                         child->exit_code = data;
210                         /* TODO: make sure any pending breakpoint is killed */
211                         wake_up_process(child);
212                         ret = 0;
213                         break;
214
215 /*
216  * make the child exit.  Best I can do is send it a sigkill. 
217  * perhaps it should be put in the status that it wants to 
218  * exit.
219  */
220                 case PTRACE_KILL:
221                         ret = 0;
222                         if (child->state == TASK_ZOMBIE) /* already dead */
223                                 break;
224                         child->exit_code = SIGKILL;
225                         /* TODO: make sure any pending breakpoint is killed */
226                         wake_up_process(child);
227                         break;
228
229                 case PTRACE_SINGLESTEP: /* set the trap flag. */
230                         ret = -EIO;
231                         if ((unsigned long) data > _NSIG)
232                                 break;
233                         child->ptrace &= ~PT_TRACESYS;
234
235                         /* TODO: set some clever breakpoint mechanism... */
236
237                         child->exit_code = data;
238                         /* give it a chance to run. */
239                         wake_up_process(child);
240                         ret = 0;
241                         break;
242
243                 case PTRACE_DETACH:
244                         ret = ptrace_detach(child, data);
245                         break;
246
247                 case PTRACE_GETREGS: { /* Get all gp regs from the child. */
248                         int i;
249                         unsigned long tmp;
250                         for (i = 0; i <= PT_MAX; i++) {
251                                 tmp = get_reg(child, i);
252                                 if (put_user(tmp, (unsigned long *) data)) {
253                                         ret = -EFAULT;
254                                         break;
255                                 }
256                                 data += sizeof(long);
257                         }
258                         ret = 0;
259                         break;
260                 }
261
262                 case PTRACE_SETREGS: { /* Set all gp regs in the child. */
263                         int i;
264                         unsigned long tmp;
265                         for (i = 0; i <= PT_MAX; i++) {
266                                 if (get_user(tmp, (unsigned long *) data)) {
267                                         ret = -EFAULT;
268                                         break;
269                                 }
270                                 if (i == PT_DCCR) {
271                                         tmp &= DCCR_MASK;
272                                         tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK;
273                                 }
274                                 put_reg(child, i, tmp);
275                                 data += sizeof(long);
276                         }
277                         ret = 0;
278                         break;
279                 }
280
281                 default:
282                         ret = -EIO;
283                         break;
284         }
285 out_tsk:
286         free_task_struct(child);
287 out:
288         unlock_kernel();
289         return ret;
290 }
291
292 asmlinkage void syscall_trace(void)
293 {
294         if ((current->ptrace & (PT_PTRACED | PT_TRACESYS)) !=
295             (PT_PTRACED | PT_TRACESYS))
296                 return;
297         /* TODO: make a way to distinguish between a syscall stop and SIGTRAP
298          * delivery like in the i386 port ? 
299          */
300         current->exit_code = SIGTRAP;
301         current->state = TASK_STOPPED;
302         notify_parent(current, SIGCHLD);
303         schedule();
304         /*
305          * this isn't the same as continuing with a signal, but it will do
306          * for normal use.  strace only continues with a signal if the
307          * stopping signal is not SIGTRAP.  -brl
308          */
309         if (current->exit_code) {
310                 send_sig(current->exit_code, current, 1);
311                 current->exit_code = 0;
312         }
313 }