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/v850/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/v850/kernel/ptrace.c')
-rw-r--r-- | arch/v850/kernel/ptrace.c | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/arch/v850/kernel/ptrace.c b/arch/v850/kernel/ptrace.c new file mode 100644 index 000000000000..8fa780757dcd --- /dev/null +++ b/arch/v850/kernel/ptrace.c | |||
@@ -0,0 +1,282 @@ | |||
1 | /* | ||
2 | * arch/v850/kernel/ptrace.c -- `ptrace' system call | ||
3 | * | ||
4 | * Copyright (C) 2002,03,04 NEC Electronics Corporation | ||
5 | * Copyright (C) 2002,03,04 Miles Bader <miles@gnu.org> | ||
6 | * | ||
7 | * Derived from arch/mips/kernel/ptrace.c: | ||
8 | * | ||
9 | * Copyright (C) 1992 Ross Biro | ||
10 | * Copyright (C) Linus Torvalds | ||
11 | * Copyright (C) 1994, 95, 96, 97, 98, 2000 Ralf Baechle | ||
12 | * Copyright (C) 1996 David S. Miller | ||
13 | * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com | ||
14 | * Copyright (C) 1999 MIPS Technologies, Inc. | ||
15 | * | ||
16 | * This file is subject to the terms and conditions of the GNU General | ||
17 | * Public License. See the file COPYING in the main directory of this | ||
18 | * archive for more details. | ||
19 | */ | ||
20 | |||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/mm.h> | ||
23 | #include <linux/sched.h> | ||
24 | #include <linux/smp_lock.h> | ||
25 | #include <linux/ptrace.h> | ||
26 | |||
27 | #include <asm/errno.h> | ||
28 | #include <asm/ptrace.h> | ||
29 | #include <asm/processor.h> | ||
30 | #include <asm/uaccess.h> | ||
31 | |||
32 | /* Returns the address where the register at REG_OFFS in P is stashed away. */ | ||
33 | static v850_reg_t *reg_save_addr (unsigned reg_offs, struct task_struct *t) | ||
34 | { | ||
35 | struct pt_regs *regs; | ||
36 | |||
37 | /* Three basic cases: | ||
38 | |||
39 | (1) A register normally saved before calling the scheduler, is | ||
40 | available in the kernel entry pt_regs structure at the top | ||
41 | of the kernel stack. The kernel trap/irq exit path takes | ||
42 | care to save/restore almost all registers for ptrace'd | ||
43 | processes. | ||
44 | |||
45 | (2) A call-clobbered register, where the process P entered the | ||
46 | kernel via [syscall] trap, is not stored anywhere; that's | ||
47 | OK, because such registers are not expected to be preserved | ||
48 | when the trap returns anyway (so we don't actually bother to | ||
49 | test for this case). | ||
50 | |||
51 | (3) A few registers not used at all by the kernel, and so | ||
52 | normally never saved except by context-switches, are in the | ||
53 | context switch state. */ | ||
54 | |||
55 | if (reg_offs == PT_CTPC || reg_offs == PT_CTPSW || reg_offs == PT_CTBP) | ||
56 | /* Register saved during context switch. */ | ||
57 | regs = thread_saved_regs (t); | ||
58 | else | ||
59 | /* Register saved during kernel entry (or not available). */ | ||
60 | regs = task_regs (t); | ||
61 | |||
62 | return (v850_reg_t *)((char *)regs + reg_offs); | ||
63 | } | ||
64 | |||
65 | /* Set the bits SET and clear the bits CLEAR in the v850e DIR | ||
66 | (`debug information register'). Returns the new value of DIR. */ | ||
67 | static inline v850_reg_t set_dir (v850_reg_t set, v850_reg_t clear) | ||
68 | { | ||
69 | register v850_reg_t rval asm ("r10"); | ||
70 | register v850_reg_t arg0 asm ("r6") = set; | ||
71 | register v850_reg_t arg1 asm ("r7") = clear; | ||
72 | |||
73 | /* The dbtrap handler has exactly this functionality when called | ||
74 | from kernel mode. 0xf840 is a `dbtrap' insn. */ | ||
75 | asm (".short 0xf840" : "=r" (rval) : "r" (arg0), "r" (arg1)); | ||
76 | |||
77 | return rval; | ||
78 | } | ||
79 | |||
80 | /* Makes sure hardware single-stepping is (globally) enabled. | ||
81 | Returns true if successful. */ | ||
82 | static inline int enable_single_stepping (void) | ||
83 | { | ||
84 | static int enabled = 0; /* Remember whether we already did it. */ | ||
85 | if (! enabled) { | ||
86 | /* Turn on the SE (`single-step enable') bit, 0x100, in the | ||
87 | DIR (`debug information register'). This may fail if a | ||
88 | processor doesn't support it or something. We also try | ||
89 | to clear bit 0x40 (`INI'), which is necessary to use the | ||
90 | debug stuff on the v850e2; on the v850e, clearing 0x40 | ||
91 | shouldn't cause any problem. */ | ||
92 | v850_reg_t dir = set_dir (0x100, 0x40); | ||
93 | /* Make sure it really got set. */ | ||
94 | if (dir & 0x100) | ||
95 | enabled = 1; | ||
96 | } | ||
97 | return enabled; | ||
98 | } | ||
99 | |||
100 | /* Try to set CHILD's single-step flag to VAL. Returns true if successful. */ | ||
101 | static int set_single_step (struct task_struct *t, int val) | ||
102 | { | ||
103 | v850_reg_t *psw_addr = reg_save_addr(PT_PSW, t); | ||
104 | if (val) { | ||
105 | /* Make sure single-stepping is enabled. */ | ||
106 | if (! enable_single_stepping ()) | ||
107 | return 0; | ||
108 | /* Set T's single-step flag. */ | ||
109 | *psw_addr |= 0x800; | ||
110 | } else | ||
111 | *psw_addr &= ~0x800; | ||
112 | return 1; | ||
113 | } | ||
114 | |||
115 | int sys_ptrace(long request, long pid, long addr, long data) | ||
116 | { | ||
117 | struct task_struct *child; | ||
118 | int rval; | ||
119 | |||
120 | lock_kernel(); | ||
121 | |||
122 | if (request == PTRACE_TRACEME) { | ||
123 | /* are we already being traced? */ | ||
124 | if (current->ptrace & PT_PTRACED) { | ||
125 | rval = -EPERM; | ||
126 | goto out; | ||
127 | } | ||
128 | /* set the ptrace bit in the process flags. */ | ||
129 | current->ptrace |= PT_PTRACED; | ||
130 | rval = 0; | ||
131 | goto out; | ||
132 | } | ||
133 | rval = -ESRCH; | ||
134 | read_lock(&tasklist_lock); | ||
135 | child = find_task_by_pid(pid); | ||
136 | if (child) | ||
137 | get_task_struct(child); | ||
138 | read_unlock(&tasklist_lock); | ||
139 | if (!child) | ||
140 | goto out; | ||
141 | |||
142 | rval = -EPERM; | ||
143 | if (pid == 1) /* you may not mess with init */ | ||
144 | goto out_tsk; | ||
145 | |||
146 | if (request == PTRACE_ATTACH) { | ||
147 | rval = ptrace_attach(child); | ||
148 | goto out_tsk; | ||
149 | } | ||
150 | rval = ptrace_check_attach(child, request == PTRACE_KILL); | ||
151 | if (rval < 0) | ||
152 | goto out_tsk; | ||
153 | |||
154 | switch (request) { | ||
155 | unsigned long val, copied; | ||
156 | |||
157 | case PTRACE_PEEKTEXT: /* read word at location addr. */ | ||
158 | case PTRACE_PEEKDATA: | ||
159 | copied = access_process_vm(child, addr, &val, sizeof(val), 0); | ||
160 | rval = -EIO; | ||
161 | if (copied != sizeof(val)) | ||
162 | break; | ||
163 | rval = put_user(val, (unsigned long *)data); | ||
164 | goto out; | ||
165 | |||
166 | case PTRACE_POKETEXT: /* write the word at location addr. */ | ||
167 | case PTRACE_POKEDATA: | ||
168 | rval = 0; | ||
169 | if (access_process_vm(child, addr, &data, sizeof(data), 1) | ||
170 | == sizeof(data)) | ||
171 | break; | ||
172 | rval = -EIO; | ||
173 | goto out; | ||
174 | |||
175 | /* Read/write the word at location ADDR in the registers. */ | ||
176 | case PTRACE_PEEKUSR: | ||
177 | case PTRACE_POKEUSR: | ||
178 | rval = 0; | ||
179 | if (addr >= PT_SIZE && request == PTRACE_PEEKUSR) { | ||
180 | /* Special requests that don't actually correspond | ||
181 | to offsets in struct pt_regs. */ | ||
182 | if (addr == PT_TEXT_ADDR) | ||
183 | val = child->mm->start_code; | ||
184 | else if (addr == PT_DATA_ADDR) | ||
185 | val = child->mm->start_data; | ||
186 | else if (addr == PT_TEXT_LEN) | ||
187 | val = child->mm->end_code | ||
188 | - child->mm->start_code; | ||
189 | else | ||
190 | rval = -EIO; | ||
191 | } else if (addr >= 0 && addr < PT_SIZE && (addr & 0x3) == 0) { | ||
192 | v850_reg_t *reg_addr = reg_save_addr(addr, child); | ||
193 | if (request == PTRACE_PEEKUSR) | ||
194 | val = *reg_addr; | ||
195 | else | ||
196 | *reg_addr = data; | ||
197 | } else | ||
198 | rval = -EIO; | ||
199 | |||
200 | if (rval == 0 && request == PTRACE_PEEKUSR) | ||
201 | rval = put_user (val, (unsigned long *)data); | ||
202 | goto out; | ||
203 | |||
204 | /* Continue and stop at next (return from) syscall */ | ||
205 | case PTRACE_SYSCALL: | ||
206 | /* Restart after a signal. */ | ||
207 | case PTRACE_CONT: | ||
208 | /* Execute a single instruction. */ | ||
209 | case PTRACE_SINGLESTEP: | ||
210 | rval = -EIO; | ||
211 | if ((unsigned long) data > _NSIG) | ||
212 | break; | ||
213 | |||
214 | /* Turn CHILD's single-step flag on or off. */ | ||
215 | if (! set_single_step (child, request == PTRACE_SINGLESTEP)) | ||
216 | break; | ||
217 | |||
218 | if (request == PTRACE_SYSCALL) | ||
219 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
220 | else | ||
221 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
222 | |||
223 | child->exit_code = data; | ||
224 | wake_up_process(child); | ||
225 | rval = 0; | ||
226 | break; | ||
227 | |||
228 | /* | ||
229 | * make the child exit. Best I can do is send it a sigkill. | ||
230 | * perhaps it should be put in the status that it wants to | ||
231 | * exit. | ||
232 | */ | ||
233 | case PTRACE_KILL: | ||
234 | rval = 0; | ||
235 | if (child->exit_state == EXIT_ZOMBIE) /* already dead */ | ||
236 | break; | ||
237 | child->exit_code = SIGKILL; | ||
238 | wake_up_process(child); | ||
239 | break; | ||
240 | |||
241 | case PTRACE_DETACH: /* detach a process that was attached. */ | ||
242 | set_single_step (child, 0); /* Clear single-step flag */ | ||
243 | rval = ptrace_detach(child, data); | ||
244 | break; | ||
245 | |||
246 | default: | ||
247 | rval = -EIO; | ||
248 | goto out; | ||
249 | } | ||
250 | |||
251 | out_tsk: | ||
252 | put_task_struct(child); | ||
253 | out: | ||
254 | unlock_kernel(); | ||
255 | return rval; | ||
256 | } | ||
257 | |||
258 | asmlinkage void syscall_trace(void) | ||
259 | { | ||
260 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | ||
261 | return; | ||
262 | if (!(current->ptrace & PT_PTRACED)) | ||
263 | return; | ||
264 | /* The 0x80 provides a way for the tracing parent to distinguish | ||
265 | between a syscall stop and SIGTRAP delivery */ | ||
266 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | ||
267 | ? 0x80 : 0)); | ||
268 | /* | ||
269 | * this isn't the same as continuing with a signal, but it will do | ||
270 | * for normal use. strace only continues with a signal if the | ||
271 | * stopping signal is not SIGTRAP. -brl | ||
272 | */ | ||
273 | if (current->exit_code) { | ||
274 | send_sig(current->exit_code, current, 1); | ||
275 | current->exit_code = 0; | ||
276 | } | ||
277 | } | ||
278 | |||
279 | void ptrace_disable (struct task_struct *child) | ||
280 | { | ||
281 | /* nothing to do */ | ||
282 | } | ||