diff options
author | David Howells <dhowells@redhat.com> | 2009-06-11 08:05:24 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-11 12:01:26 -0400 |
commit | 4a3b98932270f5d69f2c081924e356325ed704d9 (patch) | |
tree | e2a8e37a37ed7e3be32c12ea233ea18c389549d0 /arch/frv/kernel/ptrace.c | |
parent | 24ceb7e8a6c5dd6e32ac3d43a2424542c97989f5 (diff) |
FRV: Implement new-style ptrace
Implement the new-style ptrace for FRV, including adding appropriate
tracehooks.
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'arch/frv/kernel/ptrace.c')
-rw-r--r-- | arch/frv/kernel/ptrace.c | 378 |
1 files changed, 222 insertions, 156 deletions
diff --git a/arch/frv/kernel/ptrace.c b/arch/frv/kernel/ptrace.c index 6b15e5da311a..60eeed3694c0 100644 --- a/arch/frv/kernel/ptrace.c +++ b/arch/frv/kernel/ptrace.c | |||
@@ -19,6 +19,9 @@ | |||
19 | #include <linux/user.h> | 19 | #include <linux/user.h> |
20 | #include <linux/security.h> | 20 | #include <linux/security.h> |
21 | #include <linux/signal.h> | 21 | #include <linux/signal.h> |
22 | #include <linux/regset.h> | ||
23 | #include <linux/elf.h> | ||
24 | #include <linux/tracehook.h> | ||
22 | 25 | ||
23 | #include <asm/uaccess.h> | 26 | #include <asm/uaccess.h> |
24 | #include <asm/page.h> | 27 | #include <asm/page.h> |
@@ -33,6 +36,169 @@ | |||
33 | */ | 36 | */ |
34 | 37 | ||
35 | /* | 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 | |||
201 | /* | ||
36 | * Get contents of register REGNO in task TASK. | 202 | * Get contents of register REGNO in task TASK. |
37 | */ | 203 | */ |
38 | static inline long get_reg(struct task_struct *task, int regno) | 204 | static inline long get_reg(struct task_struct *task, int regno) |
@@ -69,40 +235,23 @@ static inline int put_reg(struct task_struct *task, int regno, | |||
69 | } | 235 | } |
70 | 236 | ||
71 | /* | 237 | /* |
72 | * check that an address falls within the bounds of the target process's memory | ||
73 | * mappings | ||
74 | */ | ||
75 | static inline int is_user_addr_valid(struct task_struct *child, | ||
76 | unsigned long start, unsigned long len) | ||
77 | { | ||
78 | #ifdef CONFIG_MMU | ||
79 | if (start >= PAGE_OFFSET || len > PAGE_OFFSET - start) | ||
80 | return -EIO; | ||
81 | return 0; | ||
82 | #else | ||
83 | struct vm_area_struct *vma; | ||
84 | |||
85 | vma = find_vma(child->mm, start); | ||
86 | if (vma && start >= vma->vm_start && start + len <= vma->vm_end) | ||
87 | return 0; | ||
88 | |||
89 | return -EIO; | ||
90 | #endif | ||
91 | } | ||
92 | |||
93 | /* | ||
94 | * Called by kernel/ptrace.c when detaching.. | 238 | * Called by kernel/ptrace.c when detaching.. |
95 | * | 239 | * |
96 | * Control h/w single stepping | 240 | * Control h/w single stepping |
97 | */ | 241 | */ |
98 | void ptrace_disable(struct task_struct *child) | 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) | ||
99 | { | 248 | { |
100 | child->thread.frame0->__status &= ~REG__STATUS_STEP; | 249 | child->thread.frame0->__status &= ~REG__STATUS_STEP; |
101 | } | 250 | } |
102 | 251 | ||
103 | void ptrace_enable(struct task_struct *child) | 252 | void ptrace_disable(struct task_struct *child) |
104 | { | 253 | { |
105 | child->thread.frame0->__status |= REG__STATUS_STEP; | 254 | user_disable_single_step(child); |
106 | } | 255 | } |
107 | 256 | ||
108 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | 257 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) |
@@ -111,15 +260,6 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
111 | int ret; | 260 | int ret; |
112 | 261 | ||
113 | switch (request) { | 262 | switch (request) { |
114 | /* when I and D space are separate, these will need to be fixed. */ | ||
115 | case PTRACE_PEEKTEXT: /* read word at location addr. */ | ||
116 | case PTRACE_PEEKDATA: | ||
117 | ret = -EIO; | ||
118 | if (is_user_addr_valid(child, addr, sizeof(tmp)) < 0) | ||
119 | break; | ||
120 | ret = generic_ptrace_peekdata(child, addr, data); | ||
121 | break; | ||
122 | |||
123 | /* read the word at location addr in the USER area. */ | 263 | /* read the word at location addr in the USER area. */ |
124 | case PTRACE_PEEKUSR: { | 264 | case PTRACE_PEEKUSR: { |
125 | tmp = 0; | 265 | tmp = 0; |
@@ -163,15 +303,6 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
163 | break; | 303 | break; |
164 | } | 304 | } |
165 | 305 | ||
166 | /* when I and D space are separate, this will have to be fixed. */ | ||
167 | case PTRACE_POKETEXT: /* write the word at location addr. */ | ||
168 | case PTRACE_POKEDATA: | ||
169 | ret = -EIO; | ||
170 | if (is_user_addr_valid(child, addr, sizeof(tmp)) < 0) | ||
171 | break; | ||
172 | ret = generic_ptrace_pokedata(child, addr, data); | ||
173 | break; | ||
174 | |||
175 | case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ | 306 | case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ |
176 | ret = -EIO; | 307 | ret = -EIO; |
177 | if ((addr & 3) || addr < 0) | 308 | if ((addr & 3) || addr < 0) |
@@ -179,7 +310,7 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
179 | 310 | ||
180 | ret = 0; | 311 | ret = 0; |
181 | switch (addr >> 2) { | 312 | switch (addr >> 2) { |
182 | case 0 ... PT__END-1: | 313 | case 0 ... PT__END - 1: |
183 | ret = put_reg(child, addr >> 2, data); | 314 | ret = put_reg(child, addr >> 2, data); |
184 | break; | 315 | break; |
185 | 316 | ||
@@ -189,95 +320,29 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
189 | } | 320 | } |
190 | break; | 321 | break; |
191 | 322 | ||
192 | case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ | 323 | case PTRACE_GETREGS: /* Get all integer regs from the child. */ |
193 | case PTRACE_CONT: /* restart after signal. */ | 324 | return copy_regset_to_user(child, &user_frv_native_view, |
194 | ret = -EIO; | 325 | REGSET_GENERAL, |
195 | if (!valid_signal(data)) | 326 | 0, sizeof(child->thread.user->i), |
196 | break; | 327 | (void __user *)data); |
197 | if (request == PTRACE_SYSCALL) | 328 | |
198 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | 329 | case PTRACE_SETREGS: /* Set all integer regs in the child. */ |
199 | else | 330 | return copy_regset_from_user(child, &user_frv_native_view, |
200 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | 331 | REGSET_GENERAL, |
201 | child->exit_code = data; | 332 | 0, sizeof(child->thread.user->i), |
202 | ptrace_disable(child); | 333 | (const void __user *)data); |
203 | wake_up_process(child); | 334 | |
204 | ret = 0; | 335 | case PTRACE_GETFPREGS: /* Get the child FP/Media state. */ |
205 | break; | 336 | return copy_regset_to_user(child, &user_frv_native_view, |
206 | 337 | REGSET_FPMEDIA, | |
207 | /* make the child exit. Best I can do is send it a sigkill. | 338 | 0, sizeof(child->thread.user->f), |
208 | * perhaps it should be put in the status that it wants to | 339 | (void __user *)data); |
209 | * exit. | 340 | |
210 | */ | 341 | case PTRACE_SETFPREGS: /* Set the child FP/Media state. */ |
211 | case PTRACE_KILL: | 342 | return copy_regset_from_user(child, &user_frv_native_view, |
212 | ret = 0; | 343 | REGSET_FPMEDIA, |
213 | if (child->exit_state == EXIT_ZOMBIE) /* already dead */ | 344 | 0, sizeof(child->thread.user->f), |
214 | break; | 345 | (const void __user *)data); |
215 | child->exit_code = SIGKILL; | ||
216 | clear_tsk_thread_flag(child, TIF_SINGLESTEP); | ||
217 | ptrace_disable(child); | ||
218 | wake_up_process(child); | ||
219 | break; | ||
220 | |||
221 | case PTRACE_SINGLESTEP: /* set the trap flag. */ | ||
222 | ret = -EIO; | ||
223 | if (!valid_signal(data)) | ||
224 | break; | ||
225 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
226 | ptrace_enable(child); | ||
227 | child->exit_code = data; | ||
228 | wake_up_process(child); | ||
229 | ret = 0; | ||
230 | break; | ||
231 | |||
232 | case PTRACE_DETACH: /* detach a process that was attached. */ | ||
233 | ret = ptrace_detach(child, data); | ||
234 | break; | ||
235 | |||
236 | case PTRACE_GETREGS: { /* Get all integer regs from the child. */ | ||
237 | int i; | ||
238 | for (i = 0; i < PT__GPEND; i++) { | ||
239 | tmp = get_reg(child, i); | ||
240 | if (put_user(tmp, (unsigned long *) data)) { | ||
241 | ret = -EFAULT; | ||
242 | break; | ||
243 | } | ||
244 | data += sizeof(long); | ||
245 | } | ||
246 | ret = 0; | ||
247 | break; | ||
248 | } | ||
249 | |||
250 | case PTRACE_SETREGS: { /* Set all integer regs in the child. */ | ||
251 | int i; | ||
252 | for (i = 0; i < PT__GPEND; i++) { | ||
253 | if (get_user(tmp, (unsigned long *) data)) { | ||
254 | ret = -EFAULT; | ||
255 | break; | ||
256 | } | ||
257 | put_reg(child, i, tmp); | ||
258 | data += sizeof(long); | ||
259 | } | ||
260 | ret = 0; | ||
261 | break; | ||
262 | } | ||
263 | |||
264 | case PTRACE_GETFPREGS: { /* Get the child FP/Media state. */ | ||
265 | ret = 0; | ||
266 | if (copy_to_user((void *) data, | ||
267 | &child->thread.user->f, | ||
268 | sizeof(child->thread.user->f))) | ||
269 | ret = -EFAULT; | ||
270 | break; | ||
271 | } | ||
272 | |||
273 | case PTRACE_SETFPREGS: { /* Set the child FP/Media state. */ | ||
274 | ret = 0; | ||
275 | if (copy_from_user(&child->thread.user->f, | ||
276 | (void *) data, | ||
277 | sizeof(child->thread.user->f))) | ||
278 | ret = -EFAULT; | ||
279 | break; | ||
280 | } | ||
281 | 346 | ||
282 | case PTRACE_GETFDPIC: | 347 | case PTRACE_GETFDPIC: |
283 | tmp = 0; | 348 | tmp = 0; |
@@ -300,35 +365,36 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
300 | break; | 365 | break; |
301 | 366 | ||
302 | default: | 367 | default: |
303 | ret = -EIO; | 368 | ret = ptrace_request(child, request, addr, data); |
304 | break; | 369 | break; |
305 | } | 370 | } |
306 | return ret; | 371 | return ret; |
307 | } | 372 | } |
308 | 373 | ||
309 | asmlinkage void do_syscall_trace(int leaving) | 374 | /* |
375 | * handle tracing of system call entry | ||
376 | * - return the revised system call number or ULONG_MAX to cause ENOSYS | ||
377 | */ | ||
378 | asmlinkage unsigned long syscall_trace_entry(void) | ||
310 | { | 379 | { |
311 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | 380 | __frame->__status |= REG__STATUS_SYSC_ENTRY; |
312 | return; | 381 | if (tracehook_report_syscall_entry(__frame)) { |
313 | 382 | /* tracing decided this syscall should not happen, so | |
314 | if (!(current->ptrace & PT_PTRACED)) | 383 | * We'll return a bogus call number to get an ENOSYS |
315 | return; | 384 | * error, but leave the original number in |
316 | 385 | * __frame->syscallno | |
317 | /* we need to indicate entry or exit to strace */ | 386 | */ |
318 | if (leaving) | 387 | return ULONG_MAX; |
319 | __frame->__status |= REG__STATUS_SYSC_EXIT; | 388 | } |
320 | else | ||
321 | __frame->__status |= REG__STATUS_SYSC_ENTRY; | ||
322 | 389 | ||
323 | ptrace_notify(SIGTRAP); | 390 | return __frame->syscallno; |
391 | } | ||
324 | 392 | ||
325 | /* | 393 | /* |
326 | * this isn't the same as continuing with a signal, but it will do | 394 | * handle tracing of system call exit |
327 | * for normal use. strace only continues with a signal if the | 395 | */ |
328 | * stopping signal is not SIGTRAP. -brl | 396 | asmlinkage void syscall_trace_exit(void) |
329 | */ | 397 | { |
330 | if (current->exit_code) { | 398 | __frame->__status |= REG__STATUS_SYSC_EXIT; |
331 | send_sig(current->exit_code, current, 1); | 399 | tracehook_report_syscall_exit(__frame, 0); |
332 | current->exit_code = 0; | ||
333 | } | ||
334 | } | 400 | } |