Commit | Line | Data |
---|---|---|
1da177e4 LT |
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> | |
1da177e4 LT |
9 | #include <linux/errno.h> |
10 | #include <linux/ptrace.h> | |
11 | #include <linux/user.h> | |
7ed20e1a | 12 | #include <linux/signal.h> |
5d01e6ce | 13 | #include <linux/security.h> |
1da177e4 LT |
14 | |
15 | #include <asm/uaccess.h> | |
16 | #include <asm/page.h> | |
17 | #include <asm/pgtable.h> | |
18 | #include <asm/system.h> | |
19 | #include <asm/processor.h> | |
20 | ||
21 | /* | |
22 | * Determines which bits in DCCR the user has access to. | |
23 | * 1 = access, 0 = no access. | |
24 | */ | |
25 | #define DCCR_MASK 0x0000001f /* XNZVC */ | |
26 | ||
27 | /* | |
28 | * Get contents of register REGNO in task TASK. | |
29 | */ | |
30 | inline long get_reg(struct task_struct *task, unsigned int regno) | |
31 | { | |
32 | /* USP is a special case, it's not in the pt_regs struct but | |
33 | * in the tasks thread struct | |
34 | */ | |
35 | ||
36 | if (regno == PT_USP) | |
37 | return task->thread.usp; | |
38 | else if (regno < PT_MAX) | |
95ca0dc6 | 39 | return ((unsigned long *)task_pt_regs(task))[regno]; |
1da177e4 LT |
40 | else |
41 | return 0; | |
42 | } | |
43 | ||
44 | /* | |
45 | * Write contents of register REGNO in task TASK. | |
46 | */ | |
47 | inline int put_reg(struct task_struct *task, unsigned int regno, | |
48 | unsigned long data) | |
49 | { | |
50 | if (regno == PT_USP) | |
51 | task->thread.usp = data; | |
52 | else if (regno < PT_MAX) | |
95ca0dc6 | 53 | ((unsigned long *)task_pt_regs(task))[regno] = data; |
1da177e4 LT |
54 | else |
55 | return -1; | |
56 | return 0; | |
57 | } | |
58 | ||
59 | /* | |
60 | * Called by kernel/ptrace.c when detaching. | |
61 | * | |
62 | * Make sure the single step bit is not set. | |
63 | */ | |
64 | void | |
65 | ptrace_disable(struct task_struct *child) | |
66 | { | |
67 | /* Todo - pending singlesteps? */ | |
68 | } | |
69 | ||
70 | /* | |
71 | * Note that this implementation of ptrace behaves differently from vanilla | |
72 | * ptrace. Contrary to what the man page says, in the PTRACE_PEEKTEXT, | |
73 | * PTRACE_PEEKDATA, and PTRACE_PEEKUSER requests the data variable is not | |
74 | * ignored. Instead, the data variable is expected to point at a location | |
75 | * (in user space) where the result of the ptrace call is written (instead of | |
76 | * being returned). | |
77 | */ | |
481bed45 | 78 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) |
1da177e4 | 79 | { |
1da177e4 LT |
80 | int ret; |
81 | unsigned long __user *datap = (unsigned long __user *)data; | |
82 | ||
1da177e4 LT |
83 | switch (request) { |
84 | /* Read word at location address. */ | |
85 | case PTRACE_PEEKTEXT: | |
76647323 AD |
86 | case PTRACE_PEEKDATA: |
87 | ret = generic_ptrace_peekdata(child, addr, data); | |
1da177e4 | 88 | break; |
1da177e4 LT |
89 | |
90 | /* Read the word at location address in the USER area. */ | |
91 | case PTRACE_PEEKUSR: { | |
92 | unsigned long tmp; | |
93 | ||
94 | ret = -EIO; | |
95 | if ((addr & 3) || addr < 0 || addr > PT_MAX << 2) | |
96 | break; | |
97 | ||
98 | tmp = get_reg(child, addr >> 2); | |
99 | ret = put_user(tmp, datap); | |
100 | break; | |
101 | } | |
102 | ||
103 | /* Write the word at location address. */ | |
104 | case PTRACE_POKETEXT: | |
105 | case PTRACE_POKEDATA: | |
f284ce72 | 106 | ret = generic_ptrace_pokedata(child, addr, data); |
1da177e4 LT |
107 | break; |
108 | ||
109 | /* Write the word at location address in the USER area. */ | |
110 | case PTRACE_POKEUSR: | |
111 | ret = -EIO; | |
112 | if ((addr & 3) || addr < 0 || addr > PT_MAX << 2) | |
113 | break; | |
114 | ||
115 | addr >>= 2; | |
116 | ||
117 | if (addr == PT_DCCR) { | |
118 | /* don't allow the tracing process to change stuff like | |
119 | * interrupt enable, kernel/user bit, dma enables etc. | |
120 | */ | |
121 | data &= DCCR_MASK; | |
122 | data |= get_reg(child, PT_DCCR) & ~DCCR_MASK; | |
123 | } | |
124 | if (put_reg(child, addr, data)) | |
125 | break; | |
126 | ret = 0; | |
127 | break; | |
128 | ||
129 | case PTRACE_SYSCALL: | |
130 | case PTRACE_CONT: | |
131 | ret = -EIO; | |
132 | ||
7ed20e1a | 133 | if (!valid_signal(data)) |
1da177e4 LT |
134 | break; |
135 | ||
136 | if (request == PTRACE_SYSCALL) { | |
137 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | |
138 | } | |
139 | else { | |
140 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | |
141 | } | |
142 | ||
143 | child->exit_code = data; | |
144 | ||
145 | /* TODO: make sure any pending breakpoint is killed */ | |
146 | wake_up_process(child); | |
147 | ret = 0; | |
148 | ||
149 | break; | |
150 | ||
151 | /* Make the child exit by sending it a sigkill. */ | |
152 | case PTRACE_KILL: | |
153 | ret = 0; | |
154 | ||
5d01e6ce | 155 | if (child->exit_state == EXIT_ZOMBIE) |
1da177e4 LT |
156 | break; |
157 | ||
158 | child->exit_code = SIGKILL; | |
159 | ||
160 | /* TODO: make sure any pending breakpoint is killed */ | |
161 | wake_up_process(child); | |
162 | break; | |
163 | ||
164 | /* Set the trap flag. */ | |
165 | case PTRACE_SINGLESTEP: | |
166 | ret = -EIO; | |
167 | ||
7ed20e1a | 168 | if (!valid_signal(data)) |
1da177e4 LT |
169 | break; |
170 | ||
171 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | |
172 | ||
173 | /* TODO: set some clever breakpoint mechanism... */ | |
174 | ||
175 | child->exit_code = data; | |
176 | wake_up_process(child); | |
177 | ret = 0; | |
178 | break; | |
179 | ||
180 | case PTRACE_DETACH: | |
181 | ret = ptrace_detach(child, data); | |
182 | break; | |
183 | ||
184 | /* Get all GP registers from the child. */ | |
185 | case PTRACE_GETREGS: { | |
186 | int i; | |
187 | unsigned long tmp; | |
188 | ||
c3508858 | 189 | ret = 0; |
1da177e4 LT |
190 | for (i = 0; i <= PT_MAX; i++) { |
191 | tmp = get_reg(child, i); | |
192 | ||
193 | if (put_user(tmp, datap)) { | |
194 | ret = -EFAULT; | |
c3508858 | 195 | break; |
1da177e4 LT |
196 | } |
197 | ||
198 | data += sizeof(long); | |
199 | } | |
200 | ||
1da177e4 LT |
201 | break; |
202 | } | |
203 | ||
204 | /* Set all GP registers in the child. */ | |
205 | case PTRACE_SETREGS: { | |
206 | int i; | |
207 | unsigned long tmp; | |
208 | ||
c3508858 | 209 | ret = 0; |
1da177e4 LT |
210 | for (i = 0; i <= PT_MAX; i++) { |
211 | if (get_user(tmp, datap)) { | |
212 | ret = -EFAULT; | |
c3508858 | 213 | break; |
1da177e4 LT |
214 | } |
215 | ||
216 | if (i == PT_DCCR) { | |
217 | tmp &= DCCR_MASK; | |
218 | tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK; | |
219 | } | |
220 | ||
221 | put_reg(child, i, tmp); | |
222 | data += sizeof(long); | |
223 | } | |
224 | ||
1da177e4 LT |
225 | break; |
226 | } | |
227 | ||
228 | default: | |
229 | ret = ptrace_request(child, request, addr, data); | |
230 | break; | |
231 | } | |
481bed45 | 232 | |
1da177e4 LT |
233 | return ret; |
234 | } | |
235 | ||
236 | void do_syscall_trace(void) | |
237 | { | |
238 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | |
239 | return; | |
240 | ||
241 | if (!(current->ptrace & PT_PTRACED)) | |
242 | return; | |
243 | ||
244 | /* the 0x80 provides a way for the tracing parent to distinguish | |
245 | between a syscall stop and SIGTRAP delivery */ | |
246 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | |
247 | ? 0x80 : 0)); | |
248 | ||
249 | /* | |
250 | * This isn't the same as continuing with a signal, but it will do for | |
251 | * normal use. | |
252 | */ | |
253 | if (current->exit_code) { | |
254 | send_sig(current->exit_code, current, 1); | |
255 | current->exit_code = 0; | |
256 | } | |
257 | } |