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