]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* ptrace.c: FRV specific parts of process tracing |
2 | * | |
3 | * Copyright (C) 2003-5 Red Hat, Inc. All Rights Reserved. | |
4 | * Written by David Howells (dhowells@redhat.com) | |
5 | * - Derived from arch/m68k/kernel/ptrace.c | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation; either version | |
10 | * 2 of the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
14 | #include <linux/sched.h> | |
15 | #include <linux/mm.h> | |
16 | #include <linux/smp.h> | |
1da177e4 LT |
17 | #include <linux/errno.h> |
18 | #include <linux/ptrace.h> | |
19 | #include <linux/user.h> | |
1da177e4 | 20 | #include <linux/security.h> |
7ed20e1a | 21 | #include <linux/signal.h> |
4a3b9893 DH |
22 | #include <linux/regset.h> |
23 | #include <linux/elf.h> | |
24 | #include <linux/tracehook.h> | |
1da177e4 LT |
25 | |
26 | #include <asm/uaccess.h> | |
27 | #include <asm/page.h> | |
28 | #include <asm/pgtable.h> | |
29 | #include <asm/system.h> | |
30 | #include <asm/processor.h> | |
31 | #include <asm/unistd.h> | |
32 | ||
33 | /* | |
34 | * does not yet catch signals sent when the child dies. | |
35 | * in exit.c or in signal.c. | |
36 | */ | |
37 | ||
4a3b9893 DH |
38 | /* |
39 | * retrieve the contents of FRV userspace general registers | |
40 | */ | |
41 | static int genregs_get(struct task_struct *target, | |
42 | const struct user_regset *regset, | |
43 | unsigned int pos, unsigned int count, | |
44 | void *kbuf, void __user *ubuf) | |
45 | { | |
46 | const struct user_int_regs *iregs = &target->thread.user->i; | |
47 | int ret; | |
48 | ||
49 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, | |
50 | iregs, 0, sizeof(*iregs)); | |
51 | if (ret < 0) | |
52 | return ret; | |
53 | ||
54 | return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, | |
55 | sizeof(*iregs), -1); | |
56 | } | |
57 | ||
58 | /* | |
59 | * update the contents of the FRV userspace general registers | |
60 | */ | |
61 | static int genregs_set(struct task_struct *target, | |
62 | const struct user_regset *regset, | |
63 | unsigned int pos, unsigned int count, | |
64 | const void *kbuf, const void __user *ubuf) | |
65 | { | |
66 | struct user_int_regs *iregs = &target->thread.user->i; | |
67 | unsigned int offs_gr0, offs_gr1; | |
68 | int ret; | |
69 | ||
70 | /* not allowed to set PSR or __status */ | |
71 | if (pos < offsetof(struct user_int_regs, psr) + sizeof(long) && | |
72 | pos + count > offsetof(struct user_int_regs, psr)) | |
73 | return -EIO; | |
74 | ||
75 | if (pos < offsetof(struct user_int_regs, __status) + sizeof(long) && | |
76 | pos + count > offsetof(struct user_int_regs, __status)) | |
77 | return -EIO; | |
78 | ||
79 | /* set the control regs */ | |
80 | offs_gr0 = offsetof(struct user_int_regs, gr[0]); | |
81 | offs_gr1 = offsetof(struct user_int_regs, gr[1]); | |
82 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
83 | iregs, 0, offs_gr0); | |
84 | if (ret < 0) | |
85 | return ret; | |
86 | ||
87 | /* skip GR0/TBR */ | |
88 | ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, | |
89 | offs_gr0, offs_gr1); | |
90 | if (ret < 0) | |
91 | return ret; | |
92 | ||
93 | /* set the general regs */ | |
94 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
95 | &iregs->gr[1], offs_gr1, sizeof(*iregs)); | |
96 | if (ret < 0) | |
97 | return ret; | |
98 | ||
99 | return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, | |
100 | sizeof(*iregs), -1); | |
101 | } | |
102 | ||
103 | /* | |
104 | * retrieve the contents of FRV userspace FP/Media registers | |
105 | */ | |
106 | static int fpmregs_get(struct task_struct *target, | |
107 | const struct user_regset *regset, | |
108 | unsigned int pos, unsigned int count, | |
109 | void *kbuf, void __user *ubuf) | |
110 | { | |
111 | const struct user_fpmedia_regs *fpregs = &target->thread.user->f; | |
112 | int ret; | |
113 | ||
114 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, | |
115 | fpregs, 0, sizeof(*fpregs)); | |
116 | if (ret < 0) | |
117 | return ret; | |
118 | ||
119 | return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, | |
120 | sizeof(*fpregs), -1); | |
121 | } | |
122 | ||
123 | /* | |
124 | * update the contents of the FRV userspace FP/Media registers | |
125 | */ | |
126 | static int fpmregs_set(struct task_struct *target, | |
127 | const struct user_regset *regset, | |
128 | unsigned int pos, unsigned int count, | |
129 | const void *kbuf, const void __user *ubuf) | |
130 | { | |
131 | struct user_fpmedia_regs *fpregs = &target->thread.user->f; | |
132 | int ret; | |
133 | ||
134 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | |
135 | fpregs, 0, sizeof(*fpregs)); | |
136 | if (ret < 0) | |
137 | return ret; | |
138 | ||
139 | return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, | |
140 | sizeof(*fpregs), -1); | |
141 | } | |
142 | ||
143 | /* | |
144 | * determine if the FP/Media registers have actually been used | |
145 | */ | |
146 | static int fpmregs_active(struct task_struct *target, | |
147 | const struct user_regset *regset) | |
148 | { | |
149 | return tsk_used_math(target) ? regset->n : 0; | |
150 | } | |
151 | ||
152 | /* | |
153 | * Define the register sets available on the FRV under Linux | |
154 | */ | |
155 | enum frv_regset { | |
156 | REGSET_GENERAL, | |
157 | REGSET_FPMEDIA, | |
158 | }; | |
159 | ||
160 | static const struct user_regset frv_regsets[] = { | |
161 | /* | |
162 | * General register format is: | |
163 | * PSR, ISR, CCR, CCCR, LR, LCR, PC, (STATUS), SYSCALLNO, ORIG_G8 | |
164 | * GNER0-1, IACC0, TBR, GR1-63 | |
165 | */ | |
166 | [REGSET_GENERAL] = { | |
167 | .core_note_type = NT_PRSTATUS, | |
168 | .n = ELF_NGREG, | |
169 | .size = sizeof(long), | |
170 | .align = sizeof(long), | |
171 | .get = genregs_get, | |
172 | .set = genregs_set, | |
173 | }, | |
174 | /* | |
175 | * FPU/Media register format is: | |
176 | * FR0-63, FNER0-1, MSR0-1, ACC0-7, ACCG0-8, FSR | |
177 | */ | |
178 | [REGSET_FPMEDIA] = { | |
179 | .core_note_type = NT_PRFPREG, | |
180 | .n = sizeof(struct user_fpmedia_regs) / sizeof(long), | |
181 | .size = sizeof(long), | |
182 | .align = sizeof(long), | |
183 | .get = fpmregs_get, | |
184 | .set = fpmregs_set, | |
185 | .active = fpmregs_active, | |
186 | }, | |
187 | }; | |
188 | ||
189 | static const struct user_regset_view user_frv_native_view = { | |
190 | .name = "frv", | |
191 | .e_machine = EM_FRV, | |
192 | .regsets = frv_regsets, | |
193 | .n = ARRAY_SIZE(frv_regsets), | |
194 | }; | |
195 | ||
196 | const struct user_regset_view *task_user_regset_view(struct task_struct *task) | |
197 | { | |
198 | return &user_frv_native_view; | |
199 | } | |
200 | ||
1da177e4 LT |
201 | /* |
202 | * Get contents of register REGNO in task TASK. | |
203 | */ | |
204 | static inline long get_reg(struct task_struct *task, int regno) | |
205 | { | |
206 | struct user_context *user = task->thread.user; | |
207 | ||
208 | if (regno < 0 || regno >= PT__END) | |
209 | return 0; | |
210 | ||
211 | return ((unsigned long *) user)[regno]; | |
212 | } | |
213 | ||
214 | /* | |
215 | * Write contents of register REGNO in task TASK. | |
216 | */ | |
217 | static inline int put_reg(struct task_struct *task, int regno, | |
218 | unsigned long data) | |
219 | { | |
220 | struct user_context *user = task->thread.user; | |
221 | ||
222 | if (regno < 0 || regno >= PT__END) | |
223 | return -EIO; | |
224 | ||
225 | switch (regno) { | |
226 | case PT_GR(0): | |
227 | return 0; | |
228 | case PT_PSR: | |
229 | case PT__STATUS: | |
230 | return -EIO; | |
231 | default: | |
232 | ((unsigned long *) user)[regno] = data; | |
233 | return 0; | |
234 | } | |
235 | } | |
236 | ||
1da177e4 LT |
237 | /* |
238 | * Called by kernel/ptrace.c when detaching.. | |
239 | * | |
240 | * Control h/w single stepping | |
241 | */ | |
4a3b9893 DH |
242 | void user_enable_single_step(struct task_struct *child) |
243 | { | |
244 | child->thread.frame0->__status |= REG__STATUS_STEP; | |
245 | } | |
246 | ||
247 | void user_disable_single_step(struct task_struct *child) | |
1da177e4 LT |
248 | { |
249 | child->thread.frame0->__status &= ~REG__STATUS_STEP; | |
250 | } | |
251 | ||
4a3b9893 | 252 | void ptrace_disable(struct task_struct *child) |
1da177e4 | 253 | { |
4a3b9893 | 254 | user_disable_single_step(child); |
1da177e4 LT |
255 | } |
256 | ||
9b05a69e NK |
257 | long arch_ptrace(struct task_struct *child, long request, |
258 | unsigned long addr, unsigned long data) | |
1da177e4 | 259 | { |
1da177e4 LT |
260 | unsigned long tmp; |
261 | int ret; | |
262 | ||
1da177e4 | 263 | switch (request) { |
1da177e4 LT |
264 | /* read the word at location addr in the USER area. */ |
265 | case PTRACE_PEEKUSR: { | |
266 | tmp = 0; | |
267 | ret = -EIO; | |
268 | if ((addr & 3) || addr < 0) | |
269 | break; | |
270 | ||
271 | ret = 0; | |
272 | switch (addr >> 2) { | |
273 | case 0 ... PT__END - 1: | |
274 | tmp = get_reg(child, addr >> 2); | |
275 | break; | |
276 | ||
277 | case PT__END + 0: | |
278 | tmp = child->mm->end_code - child->mm->start_code; | |
279 | break; | |
280 | ||
281 | case PT__END + 1: | |
282 | tmp = child->mm->end_data - child->mm->start_data; | |
283 | break; | |
284 | ||
285 | case PT__END + 2: | |
286 | tmp = child->mm->start_stack - child->mm->start_brk; | |
287 | break; | |
288 | ||
289 | case PT__END + 3: | |
290 | tmp = child->mm->start_code; | |
291 | break; | |
292 | ||
293 | case PT__END + 4: | |
294 | tmp = child->mm->start_stack; | |
295 | break; | |
296 | ||
297 | default: | |
298 | ret = -EIO; | |
299 | break; | |
300 | } | |
301 | ||
302 | if (ret == 0) | |
303 | ret = put_user(tmp, (unsigned long *) data); | |
304 | break; | |
305 | } | |
306 | ||
1da177e4 LT |
307 | case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ |
308 | ret = -EIO; | |
309 | if ((addr & 3) || addr < 0) | |
310 | break; | |
311 | ||
312 | ret = 0; | |
313 | switch (addr >> 2) { | |
4a3b9893 | 314 | case 0 ... PT__END - 1: |
1da177e4 LT |
315 | ret = put_reg(child, addr >> 2, data); |
316 | break; | |
317 | ||
318 | default: | |
319 | ret = -EIO; | |
320 | break; | |
321 | } | |
322 | break; | |
323 | ||
4a3b9893 DH |
324 | case PTRACE_GETREGS: /* Get all integer regs from the child. */ |
325 | return copy_regset_to_user(child, &user_frv_native_view, | |
326 | REGSET_GENERAL, | |
327 | 0, sizeof(child->thread.user->i), | |
328 | (void __user *)data); | |
329 | ||
330 | case PTRACE_SETREGS: /* Set all integer regs in the child. */ | |
331 | return copy_regset_from_user(child, &user_frv_native_view, | |
332 | REGSET_GENERAL, | |
333 | 0, sizeof(child->thread.user->i), | |
334 | (const void __user *)data); | |
335 | ||
336 | case PTRACE_GETFPREGS: /* Get the child FP/Media state. */ | |
337 | return copy_regset_to_user(child, &user_frv_native_view, | |
338 | REGSET_FPMEDIA, | |
339 | 0, sizeof(child->thread.user->f), | |
340 | (void __user *)data); | |
341 | ||
342 | case PTRACE_SETFPREGS: /* Set the child FP/Media state. */ | |
343 | return copy_regset_from_user(child, &user_frv_native_view, | |
344 | REGSET_FPMEDIA, | |
345 | 0, sizeof(child->thread.user->f), | |
346 | (const void __user *)data); | |
1da177e4 | 347 | |
1da177e4 | 348 | default: |
4a3b9893 | 349 | ret = ptrace_request(child, request, addr, data); |
1da177e4 LT |
350 | break; |
351 | } | |
1da177e4 LT |
352 | return ret; |
353 | } | |
354 | ||
4a3b9893 DH |
355 | /* |
356 | * handle tracing of system call entry | |
357 | * - return the revised system call number or ULONG_MAX to cause ENOSYS | |
358 | */ | |
359 | asmlinkage unsigned long syscall_trace_entry(void) | |
1da177e4 | 360 | { |
4a3b9893 DH |
361 | __frame->__status |= REG__STATUS_SYSC_ENTRY; |
362 | if (tracehook_report_syscall_entry(__frame)) { | |
363 | /* tracing decided this syscall should not happen, so | |
364 | * We'll return a bogus call number to get an ENOSYS | |
365 | * error, but leave the original number in | |
366 | * __frame->syscallno | |
367 | */ | |
368 | return ULONG_MAX; | |
369 | } | |
1da177e4 | 370 | |
4a3b9893 DH |
371 | return __frame->syscallno; |
372 | } | |
1da177e4 | 373 | |
4a3b9893 DH |
374 | /* |
375 | * handle tracing of system call exit | |
376 | */ | |
377 | asmlinkage void syscall_trace_exit(void) | |
378 | { | |
379 | __frame->__status |= REG__STATUS_SYSC_EXIT; | |
380 | tracehook_report_syscall_exit(__frame, 0); | |
1da177e4 | 381 | } |