diff options
Diffstat (limited to 'arch/blackfin/kernel/ptrace.c')
-rw-r--r-- | arch/blackfin/kernel/ptrace.c | 360 |
1 files changed, 165 insertions, 195 deletions
diff --git a/arch/blackfin/kernel/ptrace.c b/arch/blackfin/kernel/ptrace.c index 65567dc4b9f5..43eb969405d1 100644 --- a/arch/blackfin/kernel/ptrace.c +++ b/arch/blackfin/kernel/ptrace.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | * linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds | 2 | * linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds |
3 | * these modifications are Copyright 2004-2009 Analog Devices Inc. | 3 | * these modifications are Copyright 2004-2010 Analog Devices Inc. |
4 | * | 4 | * |
5 | * Licensed under the GPL-2 | 5 | * Licensed under the GPL-2 |
6 | */ | 6 | */ |
@@ -9,10 +9,13 @@ | |||
9 | #include <linux/sched.h> | 9 | #include <linux/sched.h> |
10 | #include <linux/mm.h> | 10 | #include <linux/mm.h> |
11 | #include <linux/smp.h> | 11 | #include <linux/smp.h> |
12 | #include <linux/elf.h> | ||
12 | #include <linux/errno.h> | 13 | #include <linux/errno.h> |
13 | #include <linux/ptrace.h> | 14 | #include <linux/ptrace.h> |
14 | #include <linux/user.h> | 15 | #include <linux/user.h> |
16 | #include <linux/regset.h> | ||
15 | #include <linux/signal.h> | 17 | #include <linux/signal.h> |
18 | #include <linux/tracehook.h> | ||
16 | #include <linux/uaccess.h> | 19 | #include <linux/uaccess.h> |
17 | 20 | ||
18 | #include <asm/page.h> | 21 | #include <asm/page.h> |
@@ -25,90 +28,57 @@ | |||
25 | #include <asm/cacheflush.h> | 28 | #include <asm/cacheflush.h> |
26 | #include <asm/mem_map.h> | 29 | #include <asm/mem_map.h> |
27 | 30 | ||
28 | #define TEXT_OFFSET 0 | ||
29 | /* | 31 | /* |
30 | * does not yet catch signals sent when the child dies. | 32 | * does not yet catch signals sent when the child dies. |
31 | * in exit.c or in signal.c. | 33 | * in exit.c or in signal.c. |
32 | */ | 34 | */ |
33 | 35 | ||
34 | /* determines which bits in the SYSCFG reg the user has access to. */ | ||
35 | /* 1 = access 0 = no access */ | ||
36 | #define SYSCFG_MASK 0x0007 /* SYSCFG reg */ | ||
37 | /* sets the trace bits. */ | ||
38 | #define TRACE_BITS 0x0001 | ||
39 | |||
40 | /* Find the stack offset for a register, relative to thread.esp0. */ | ||
41 | #define PT_REG(reg) ((long)&((struct pt_regs *)0)->reg) | ||
42 | |||
43 | /* | ||
44 | * Get the address of the live pt_regs for the specified task. | ||
45 | * These are saved onto the top kernel stack when the process | ||
46 | * is not running. | ||
47 | * | ||
48 | * Note: if a user thread is execve'd from kernel space, the | ||
49 | * kernel stack will not be empty on entry to the kernel, so | ||
50 | * ptracing these tasks will fail. | ||
51 | */ | ||
52 | static inline struct pt_regs *get_user_regs(struct task_struct *task) | ||
53 | { | ||
54 | return (struct pt_regs *) | ||
55 | ((unsigned long)task_stack_page(task) + | ||
56 | (THREAD_SIZE - sizeof(struct pt_regs))); | ||
57 | } | ||
58 | |||
59 | /* | ||
60 | * Get all user integer registers. | ||
61 | */ | ||
62 | static inline int ptrace_getregs(struct task_struct *tsk, void __user *uregs) | ||
63 | { | ||
64 | struct pt_regs regs; | ||
65 | memcpy(®s, get_user_regs(tsk), sizeof(regs)); | ||
66 | regs.usp = tsk->thread.usp; | ||
67 | return copy_to_user(uregs, ®s, sizeof(struct pt_regs)) ? -EFAULT : 0; | ||
68 | } | ||
69 | |||
70 | /* Mapping from PT_xxx to the stack offset at which the register is | ||
71 | * saved. Notice that usp has no stack-slot and needs to be treated | ||
72 | * specially (see get_reg/put_reg below). | ||
73 | */ | ||
74 | |||
75 | /* | 36 | /* |
76 | * Get contents of register REGNO in task TASK. | 37 | * Get contents of register REGNO in task TASK. |
77 | */ | 38 | */ |
78 | static inline long get_reg(struct task_struct *task, int regno) | 39 | static inline long |
40 | get_reg(struct task_struct *task, long regno, unsigned long __user *datap) | ||
79 | { | 41 | { |
80 | unsigned char *reg_ptr; | 42 | long tmp; |
43 | struct pt_regs *regs = task_pt_regs(task); | ||
81 | 44 | ||
82 | struct pt_regs *regs = | 45 | if (regno & 3 || regno > PT_LAST_PSEUDO || regno < 0) |
83 | (struct pt_regs *)((unsigned long)task_stack_page(task) + | 46 | return -EIO; |
84 | (THREAD_SIZE - sizeof(struct pt_regs))); | ||
85 | reg_ptr = (char *)regs; | ||
86 | 47 | ||
87 | switch (regno) { | 48 | switch (regno) { |
49 | case PT_TEXT_ADDR: | ||
50 | tmp = task->mm->start_code; | ||
51 | break; | ||
52 | case PT_TEXT_END_ADDR: | ||
53 | tmp = task->mm->end_code; | ||
54 | break; | ||
55 | case PT_DATA_ADDR: | ||
56 | tmp = task->mm->start_data; | ||
57 | break; | ||
88 | case PT_USP: | 58 | case PT_USP: |
89 | return task->thread.usp; | 59 | tmp = task->thread.usp; |
60 | break; | ||
90 | default: | 61 | default: |
91 | if (regno <= 216) | 62 | if (regno < sizeof(*regs)) { |
92 | return *(long *)(reg_ptr + regno); | 63 | void *reg_ptr = regs; |
64 | tmp = *(long *)(reg_ptr + regno); | ||
65 | } else | ||
66 | return -EIO; | ||
93 | } | 67 | } |
94 | /* slight mystery ... never seems to come here but kernel misbehaves without this code! */ | ||
95 | 68 | ||
96 | printk(KERN_WARNING "Request to get for unknown register %d\n", regno); | 69 | return put_user(tmp, datap); |
97 | return 0; | ||
98 | } | 70 | } |
99 | 71 | ||
100 | /* | 72 | /* |
101 | * Write contents of register REGNO in task TASK. | 73 | * Write contents of register REGNO in task TASK. |
102 | */ | 74 | */ |
103 | static inline int | 75 | static inline int |
104 | put_reg(struct task_struct *task, int regno, unsigned long data) | 76 | put_reg(struct task_struct *task, long regno, unsigned long data) |
105 | { | 77 | { |
106 | char *reg_ptr; | 78 | struct pt_regs *regs = task_pt_regs(task); |
107 | 79 | ||
108 | struct pt_regs *regs = | 80 | if (regno & 3 || regno > PT_LAST_PSEUDO || regno < 0) |
109 | (struct pt_regs *)((unsigned long)task_stack_page(task) + | 81 | return -EIO; |
110 | (THREAD_SIZE - sizeof(struct pt_regs))); | ||
111 | reg_ptr = (char *)regs; | ||
112 | 82 | ||
113 | switch (regno) { | 83 | switch (regno) { |
114 | case PT_PC: | 84 | case PT_PC: |
@@ -125,10 +95,18 @@ put_reg(struct task_struct *task, int regno, unsigned long data) | |||
125 | regs->usp = data; | 95 | regs->usp = data; |
126 | task->thread.usp = data; | 96 | task->thread.usp = data; |
127 | break; | 97 | break; |
98 | case PT_SYSCFG: /* don't let userspace screw with this */ | ||
99 | if ((data & ~1) != 0x6) | ||
100 | pr_warning("ptrace: ignore syscfg write of %#lx\n", data); | ||
101 | break; /* regs->syscfg = data; break; */ | ||
128 | default: | 102 | default: |
129 | if (regno <= 216) | 103 | if (regno < sizeof(*regs)) { |
130 | *(long *)(reg_ptr + regno) = data; | 104 | void *reg_offset = regs; |
105 | *(long *)(reg_offset + regno) = data; | ||
106 | } | ||
107 | /* Ignore writes to pseudo registers */ | ||
131 | } | 108 | } |
109 | |||
132 | return 0; | 110 | return 0; |
133 | } | 111 | } |
134 | 112 | ||
@@ -160,24 +138,98 @@ static inline int is_user_addr_valid(struct task_struct *child, | |||
160 | return -EIO; | 138 | return -EIO; |
161 | } | 139 | } |
162 | 140 | ||
163 | void ptrace_enable(struct task_struct *child) | 141 | /* |
142 | * retrieve the contents of Blackfin userspace general registers | ||
143 | */ | ||
144 | static int genregs_get(struct task_struct *target, | ||
145 | const struct user_regset *regset, | ||
146 | unsigned int pos, unsigned int count, | ||
147 | void *kbuf, void __user *ubuf) | ||
164 | { | 148 | { |
165 | unsigned long tmp; | 149 | struct pt_regs *regs = task_pt_regs(target); |
166 | tmp = get_reg(child, PT_SYSCFG) | (TRACE_BITS); | 150 | int ret; |
167 | put_reg(child, PT_SYSCFG, tmp); | 151 | |
152 | /* This sucks ... */ | ||
153 | regs->usp = target->thread.usp; | ||
154 | |||
155 | ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, | ||
156 | regs, 0, sizeof(*regs)); | ||
157 | if (ret < 0) | ||
158 | return ret; | ||
159 | |||
160 | return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, | ||
161 | sizeof(*regs), -1); | ||
168 | } | 162 | } |
169 | 163 | ||
170 | /* | 164 | /* |
171 | * Called by kernel/ptrace.c when detaching.. | 165 | * update the contents of the Blackfin userspace general registers |
172 | * | 166 | */ |
173 | * Make sure the single step bit is not set. | 167 | static int genregs_set(struct task_struct *target, |
168 | const struct user_regset *regset, | ||
169 | unsigned int pos, unsigned int count, | ||
170 | const void *kbuf, const void __user *ubuf) | ||
171 | { | ||
172 | struct pt_regs *regs = task_pt_regs(target); | ||
173 | int ret; | ||
174 | |||
175 | /* Don't let people set SYSCFG (it's at the end of pt_regs) */ | ||
176 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, | ||
177 | regs, 0, PT_SYSCFG); | ||
178 | if (ret < 0) | ||
179 | return ret; | ||
180 | |||
181 | /* This sucks ... */ | ||
182 | target->thread.usp = regs->usp; | ||
183 | /* regs->retx = regs->pc; */ | ||
184 | |||
185 | return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, | ||
186 | PT_SYSCFG, -1); | ||
187 | } | ||
188 | |||
189 | /* | ||
190 | * Define the register sets available on the Blackfin under Linux | ||
174 | */ | 191 | */ |
175 | void ptrace_disable(struct task_struct *child) | 192 | enum bfin_regset { |
193 | REGSET_GENERAL, | ||
194 | }; | ||
195 | |||
196 | static const struct user_regset bfin_regsets[] = { | ||
197 | [REGSET_GENERAL] = { | ||
198 | .core_note_type = NT_PRSTATUS, | ||
199 | .n = sizeof(struct pt_regs) / sizeof(long), | ||
200 | .size = sizeof(long), | ||
201 | .align = sizeof(long), | ||
202 | .get = genregs_get, | ||
203 | .set = genregs_set, | ||
204 | }, | ||
205 | }; | ||
206 | |||
207 | static const struct user_regset_view user_bfin_native_view = { | ||
208 | .name = "Blackfin", | ||
209 | .e_machine = EM_BLACKFIN, | ||
210 | .regsets = bfin_regsets, | ||
211 | .n = ARRAY_SIZE(bfin_regsets), | ||
212 | }; | ||
213 | |||
214 | const struct user_regset_view *task_user_regset_view(struct task_struct *task) | ||
215 | { | ||
216 | return &user_bfin_native_view; | ||
217 | } | ||
218 | |||
219 | void user_enable_single_step(struct task_struct *child) | ||
220 | { | ||
221 | struct pt_regs *regs = task_pt_regs(child); | ||
222 | regs->syscfg |= SYSCFG_SSSTEP; | ||
223 | |||
224 | set_tsk_thread_flag(child, TIF_SINGLESTEP); | ||
225 | } | ||
226 | |||
227 | void user_disable_single_step(struct task_struct *child) | ||
176 | { | 228 | { |
177 | unsigned long tmp; | 229 | struct pt_regs *regs = task_pt_regs(child); |
178 | /* make sure the single step bit is not set. */ | 230 | regs->syscfg &= ~SYSCFG_SSSTEP; |
179 | tmp = get_reg(child, PT_SYSCFG) & ~TRACE_BITS; | 231 | |
180 | put_reg(child, PT_SYSCFG, tmp); | 232 | clear_tsk_thread_flag(child, TIF_SINGLESTEP); |
181 | } | 233 | } |
182 | 234 | ||
183 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | 235 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) |
@@ -240,40 +292,6 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
240 | break; | 292 | break; |
241 | } | 293 | } |
242 | 294 | ||
243 | /* read the word at location addr in the USER area. */ | ||
244 | case PTRACE_PEEKUSR: | ||
245 | { | ||
246 | unsigned long tmp; | ||
247 | ret = -EIO; | ||
248 | tmp = 0; | ||
249 | if ((addr & 3) || (addr > (sizeof(struct pt_regs) + 16))) { | ||
250 | printk(KERN_WARNING "ptrace error : PEEKUSR : temporarily returning " | ||
251 | "0 - %x sizeof(pt_regs) is %lx\n", | ||
252 | (int)addr, sizeof(struct pt_regs)); | ||
253 | break; | ||
254 | } | ||
255 | if (addr == sizeof(struct pt_regs)) { | ||
256 | /* PT_TEXT_ADDR */ | ||
257 | tmp = child->mm->start_code + TEXT_OFFSET; | ||
258 | } else if (addr == (sizeof(struct pt_regs) + 4)) { | ||
259 | /* PT_TEXT_END_ADDR */ | ||
260 | tmp = child->mm->end_code; | ||
261 | } else if (addr == (sizeof(struct pt_regs) + 8)) { | ||
262 | /* PT_DATA_ADDR */ | ||
263 | tmp = child->mm->start_data; | ||
264 | #ifdef CONFIG_BINFMT_ELF_FDPIC | ||
265 | } else if (addr == (sizeof(struct pt_regs) + 12)) { | ||
266 | goto case_PTRACE_GETFDPIC_EXEC; | ||
267 | } else if (addr == (sizeof(struct pt_regs) + 16)) { | ||
268 | goto case_PTRACE_GETFDPIC_INTERP; | ||
269 | #endif | ||
270 | } else { | ||
271 | tmp = get_reg(child, addr); | ||
272 | } | ||
273 | ret = put_user(tmp, datap); | ||
274 | break; | ||
275 | } | ||
276 | |||
277 | #ifdef CONFIG_BINFMT_ELF_FDPIC | 295 | #ifdef CONFIG_BINFMT_ELF_FDPIC |
278 | case PTRACE_GETFDPIC: { | 296 | case PTRACE_GETFDPIC: { |
279 | unsigned long tmp = 0; | 297 | unsigned long tmp = 0; |
@@ -336,78 +354,36 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
336 | break; | 354 | break; |
337 | } | 355 | } |
338 | 356 | ||
339 | case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ | 357 | case PTRACE_PEEKUSR: |
340 | ret = -EIO; | 358 | switch (addr) { |
341 | if ((addr & 3) || (addr > (sizeof(struct pt_regs) + 16))) { | 359 | #ifdef CONFIG_BINFMT_ELF_FDPIC /* backwards compat */ |
342 | printk(KERN_WARNING "ptrace error : POKEUSR: temporarily returning 0\n"); | 360 | case PT_FDPIC_EXEC: goto case_PTRACE_GETFDPIC_EXEC; |
343 | break; | 361 | case PT_FDPIC_INTERP: goto case_PTRACE_GETFDPIC_INTERP; |
344 | } | 362 | #endif |
345 | 363 | default: | |
346 | if (addr >= (sizeof(struct pt_regs))) { | 364 | ret = get_reg(child, addr, datap); |
347 | ret = 0; | ||
348 | break; | ||
349 | } | ||
350 | if (addr == PT_SYSCFG) { | ||
351 | data &= SYSCFG_MASK; | ||
352 | data |= get_reg(child, PT_SYSCFG); | ||
353 | } | 365 | } |
354 | ret = put_reg(child, addr, data); | 366 | pr_debug("ptrace: PEEKUSR reg %li with %#lx = %i\n", addr, data, ret); |
355 | break; | 367 | break; |
356 | 368 | ||
357 | case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ | 369 | case PTRACE_POKEUSR: |
358 | case PTRACE_CONT: /* restart after signal. */ | 370 | ret = put_reg(child, addr, data); |
359 | pr_debug("ptrace: syscall/cont\n"); | 371 | pr_debug("ptrace: POKEUSR reg %li with %li = %i\n", addr, data, ret); |
360 | |||
361 | ret = -EIO; | ||
362 | if (!valid_signal(data)) | ||
363 | break; | ||
364 | if (request == PTRACE_SYSCALL) | ||
365 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
366 | else | ||
367 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
368 | child->exit_code = data; | ||
369 | ptrace_disable(child); | ||
370 | pr_debug("ptrace: before wake_up_process\n"); | ||
371 | wake_up_process(child); | ||
372 | ret = 0; | ||
373 | break; | ||
374 | |||
375 | /* | ||
376 | * make the child exit. Best I can do is send it a sigkill. | ||
377 | * perhaps it should be put in the status that it wants to | ||
378 | * exit. | ||
379 | */ | ||
380 | case PTRACE_KILL: | ||
381 | ret = 0; | ||
382 | if (child->exit_state == EXIT_ZOMBIE) /* already dead */ | ||
383 | break; | ||
384 | child->exit_code = SIGKILL; | ||
385 | ptrace_disable(child); | ||
386 | wake_up_process(child); | ||
387 | break; | ||
388 | |||
389 | case PTRACE_SINGLESTEP: /* set the trap flag. */ | ||
390 | pr_debug("ptrace: single step\n"); | ||
391 | ret = -EIO; | ||
392 | if (!valid_signal(data)) | ||
393 | break; | ||
394 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
395 | ptrace_enable(child); | ||
396 | child->exit_code = data; | ||
397 | wake_up_process(child); | ||
398 | ret = 0; | ||
399 | break; | 372 | break; |
400 | 373 | ||
401 | case PTRACE_GETREGS: | 374 | case PTRACE_GETREGS: |
402 | /* Get all gp regs from the child. */ | 375 | pr_debug("ptrace: PTRACE_GETREGS\n"); |
403 | ret = ptrace_getregs(child, datap); | 376 | return copy_regset_to_user(child, &user_bfin_native_view, |
404 | break; | 377 | REGSET_GENERAL, |
378 | 0, sizeof(struct pt_regs), | ||
379 | (void __user *)data); | ||
405 | 380 | ||
406 | case PTRACE_SETREGS: | 381 | case PTRACE_SETREGS: |
407 | printk(KERN_WARNING "ptrace: SETREGS: **** NOT IMPLEMENTED ***\n"); | 382 | pr_debug("ptrace: PTRACE_SETREGS\n"); |
408 | /* Set all gp regs in the child. */ | 383 | return copy_regset_from_user(child, &user_bfin_native_view, |
409 | ret = 0; | 384 | REGSET_GENERAL, |
410 | break; | 385 | 0, sizeof(struct pt_regs), |
386 | (const void __user *)data); | ||
411 | 387 | ||
412 | default: | 388 | default: |
413 | ret = ptrace_request(child, request, addr, data); | 389 | ret = ptrace_request(child, request, addr, data); |
@@ -417,27 +393,21 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
417 | return ret; | 393 | return ret; |
418 | } | 394 | } |
419 | 395 | ||
420 | asmlinkage void syscall_trace(void) | 396 | asmlinkage int syscall_trace_enter(struct pt_regs *regs) |
421 | { | 397 | { |
422 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | 398 | int ret = 0; |
423 | return; | 399 | |
424 | 400 | if (test_thread_flag(TIF_SYSCALL_TRACE)) | |
425 | if (!(current->ptrace & PT_PTRACED)) | 401 | ret = tracehook_report_syscall_entry(regs); |
426 | return; | 402 | |
427 | 403 | return ret; | |
428 | /* the 0x80 provides a way for the tracing parent to distinguish | 404 | } |
429 | * between a syscall stop and SIGTRAP delivery | 405 | |
430 | */ | 406 | asmlinkage void syscall_trace_leave(struct pt_regs *regs) |
431 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | 407 | { |
432 | ? 0x80 : 0)); | 408 | int step; |
433 | 409 | ||
434 | /* | 410 | step = test_thread_flag(TIF_SINGLESTEP); |
435 | * this isn't the same as continuing with a signal, but it will do | 411 | if (step || test_thread_flag(TIF_SYSCALL_TRACE)) |
436 | * for normal use. strace only continues with a signal if the | 412 | tracehook_report_syscall_exit(regs, step); |
437 | * stopping signal is not SIGTRAP. -brl | ||
438 | */ | ||
439 | if (current->exit_code) { | ||
440 | send_sig(current->exit_code, current, 1); | ||
441 | current->exit_code = 0; | ||
442 | } | ||
443 | } | 413 | } |