aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWill Deacon <will.deacon@arm.com>2011-02-14 08:31:09 -0500
committerRussell King <rmk+kernel@arm.linux.org.uk>2011-02-23 12:24:22 -0500
commit425fc47adb5bb69f76285be77a09a3341a30799e (patch)
treeaa1fab1e90520f80573445c77b55db8446e3570c
parent5637a126482026b37d426d76e1b18f748f309aaa (diff)
ARM: 6668/1: ptrace: remove single-step emulation code
PTRACE_SINGLESTEP is a ptrace request designed to offer single-stepping support to userspace when the underlying architecture has hardware support for this operation. On ARM, we set arch_has_single_step() to 1 and attempt to emulate hardware single-stepping by disassembling the current instruction to determine the next pc and placing a software breakpoint on that location. Unfortunately this has the following problems: 1.) Only a subset of ARMv7 instructions are supported 2.) Thumb-2 is unsupported 3.) The code is not SMP safe We could try to fix this code, but it turns out that because of the above issues it is rarely used in practice. GDB, for example, uses PTRACE_POKETEXT and PTRACE_PEEKTEXT to manage breakpoints itself and does not require any kernel assistance. This patch removes the single-step emulation code from ptrace meaning that the PTRACE_SINGLESTEP request will return -EIO on ARM. Portable code must check the return value from a ptrace call and handle the failure gracefully. Acked-by: Nicolas Pitre <nicolas.pitre@linaro.org> Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r--arch/arm/include/asm/processor.h12
-rw-r--r--arch/arm/include/asm/ptrace.h2
-rw-r--r--arch/arm/include/asm/traps.h1
-rw-r--r--arch/arm/kernel/ptrace.c383
-rw-r--r--arch/arm/kernel/ptrace.h37
-rw-r--r--arch/arm/kernel/signal.c9
-rw-r--r--arch/arm/kernel/traps.c2
7 files changed, 3 insertions, 443 deletions
diff --git a/arch/arm/include/asm/processor.h b/arch/arm/include/asm/processor.h
index 67357baaeee..b439b41aeac 100644
--- a/arch/arm/include/asm/processor.h
+++ b/arch/arm/include/asm/processor.h
@@ -29,19 +29,7 @@
29#define STACK_TOP_MAX TASK_SIZE 29#define STACK_TOP_MAX TASK_SIZE
30#endif 30#endif
31 31
32union debug_insn {
33 u32 arm;
34 u16 thumb;
35};
36
37struct debug_entry {
38 u32 address;
39 union debug_insn insn;
40};
41
42struct debug_info { 32struct debug_info {
43 int nsaved;
44 struct debug_entry bp[2];
45#ifdef CONFIG_HAVE_HW_BREAKPOINT 33#ifdef CONFIG_HAVE_HW_BREAKPOINT
46 struct perf_event *hbp[ARM_MAX_HBP_SLOTS]; 34 struct perf_event *hbp[ARM_MAX_HBP_SLOTS];
47#endif 35#endif
diff --git a/arch/arm/include/asm/ptrace.h b/arch/arm/include/asm/ptrace.h
index 783d50f3261..a8ff22b2a39 100644
--- a/arch/arm/include/asm/ptrace.h
+++ b/arch/arm/include/asm/ptrace.h
@@ -130,8 +130,6 @@ struct pt_regs {
130 130
131#ifdef __KERNEL__ 131#ifdef __KERNEL__
132 132
133#define arch_has_single_step() (1)
134
135#define user_mode(regs) \ 133#define user_mode(regs) \
136 (((regs)->ARM_cpsr & 0xf) == 0) 134 (((regs)->ARM_cpsr & 0xf) == 0)
137 135
diff --git a/arch/arm/include/asm/traps.h b/arch/arm/include/asm/traps.h
index 1b960d5ef6a..f90756dc16d 100644
--- a/arch/arm/include/asm/traps.h
+++ b/arch/arm/include/asm/traps.h
@@ -45,6 +45,7 @@ static inline int in_exception_text(unsigned long ptr)
45 45
46extern void __init early_trap_init(void); 46extern void __init early_trap_init(void);
47extern void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame); 47extern void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame);
48extern void ptrace_break(struct task_struct *tsk, struct pt_regs *regs);
48 49
49extern void *vectors_page; 50extern void *vectors_page;
50 51
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
index 19c6816db61..eace844511f 100644
--- a/arch/arm/kernel/ptrace.c
+++ b/arch/arm/kernel/ptrace.c
@@ -26,8 +26,6 @@
26#include <asm/system.h> 26#include <asm/system.h>
27#include <asm/traps.h> 27#include <asm/traps.h>
28 28
29#include "ptrace.h"
30
31#define REG_PC 15 29#define REG_PC 15
32#define REG_PSR 16 30#define REG_PSR 16
33/* 31/*
@@ -184,389 +182,12 @@ put_user_reg(struct task_struct *task, int offset, long data)
184 return ret; 182 return ret;
185} 183}
186 184
187static inline int
188read_u32(struct task_struct *task, unsigned long addr, u32 *res)
189{
190 int ret;
191
192 ret = access_process_vm(task, addr, res, sizeof(*res), 0);
193
194 return ret == sizeof(*res) ? 0 : -EIO;
195}
196
197static inline int
198read_instr(struct task_struct *task, unsigned long addr, u32 *res)
199{
200 int ret;
201
202 if (addr & 1) {
203 u16 val;
204 ret = access_process_vm(task, addr & ~1, &val, sizeof(val), 0);
205 ret = ret == sizeof(val) ? 0 : -EIO;
206 *res = val;
207 } else {
208 u32 val;
209 ret = access_process_vm(task, addr & ~3, &val, sizeof(val), 0);
210 ret = ret == sizeof(val) ? 0 : -EIO;
211 *res = val;
212 }
213 return ret;
214}
215
216/*
217 * Get value of register `rn' (in the instruction)
218 */
219static unsigned long
220ptrace_getrn(struct task_struct *child, unsigned long insn)
221{
222 unsigned int reg = (insn >> 16) & 15;
223 unsigned long val;
224
225 val = get_user_reg(child, reg);
226 if (reg == 15)
227 val += 8;
228
229 return val;
230}
231
232/*
233 * Get value of operand 2 (in an ALU instruction)
234 */
235static unsigned long
236ptrace_getaluop2(struct task_struct *child, unsigned long insn)
237{
238 unsigned long val;
239 int shift;
240 int type;
241
242 if (insn & 1 << 25) {
243 val = insn & 255;
244 shift = (insn >> 8) & 15;
245 type = 3;
246 } else {
247 val = get_user_reg (child, insn & 15);
248
249 if (insn & (1 << 4))
250 shift = (int)get_user_reg (child, (insn >> 8) & 15);
251 else
252 shift = (insn >> 7) & 31;
253
254 type = (insn >> 5) & 3;
255 }
256
257 switch (type) {
258 case 0: val <<= shift; break;
259 case 1: val >>= shift; break;
260 case 2:
261 val = (((signed long)val) >> shift);
262 break;
263 case 3:
264 val = (val >> shift) | (val << (32 - shift));
265 break;
266 }
267 return val;
268}
269
270/*
271 * Get value of operand 2 (in a LDR instruction)
272 */
273static unsigned long
274ptrace_getldrop2(struct task_struct *child, unsigned long insn)
275{
276 unsigned long val;
277 int shift;
278 int type;
279
280 val = get_user_reg(child, insn & 15);
281 shift = (insn >> 7) & 31;
282 type = (insn >> 5) & 3;
283
284 switch (type) {
285 case 0: val <<= shift; break;
286 case 1: val >>= shift; break;
287 case 2:
288 val = (((signed long)val) >> shift);
289 break;
290 case 3:
291 val = (val >> shift) | (val << (32 - shift));
292 break;
293 }
294 return val;
295}
296
297#define OP_MASK 0x01e00000
298#define OP_AND 0x00000000
299#define OP_EOR 0x00200000
300#define OP_SUB 0x00400000
301#define OP_RSB 0x00600000
302#define OP_ADD 0x00800000
303#define OP_ADC 0x00a00000
304#define OP_SBC 0x00c00000
305#define OP_RSC 0x00e00000
306#define OP_ORR 0x01800000
307#define OP_MOV 0x01a00000
308#define OP_BIC 0x01c00000
309#define OP_MVN 0x01e00000
310
311static unsigned long
312get_branch_address(struct task_struct *child, unsigned long pc, unsigned long insn)
313{
314 u32 alt = 0;
315
316 switch (insn & 0x0e000000) {
317 case 0x00000000:
318 case 0x02000000: {
319 /*
320 * data processing
321 */
322 long aluop1, aluop2, ccbit;
323
324 if ((insn & 0x0fffffd0) == 0x012fff10) {
325 /*
326 * bx or blx
327 */
328 alt = get_user_reg(child, insn & 15);
329 break;
330 }
331
332
333 if ((insn & 0xf000) != 0xf000)
334 break;
335
336 aluop1 = ptrace_getrn(child, insn);
337 aluop2 = ptrace_getaluop2(child, insn);
338 ccbit = get_user_reg(child, REG_PSR) & PSR_C_BIT ? 1 : 0;
339
340 switch (insn & OP_MASK) {
341 case OP_AND: alt = aluop1 & aluop2; break;
342 case OP_EOR: alt = aluop1 ^ aluop2; break;
343 case OP_SUB: alt = aluop1 - aluop2; break;
344 case OP_RSB: alt = aluop2 - aluop1; break;
345 case OP_ADD: alt = aluop1 + aluop2; break;
346 case OP_ADC: alt = aluop1 + aluop2 + ccbit; break;
347 case OP_SBC: alt = aluop1 - aluop2 + ccbit; break;
348 case OP_RSC: alt = aluop2 - aluop1 + ccbit; break;
349 case OP_ORR: alt = aluop1 | aluop2; break;
350 case OP_MOV: alt = aluop2; break;
351 case OP_BIC: alt = aluop1 & ~aluop2; break;
352 case OP_MVN: alt = ~aluop2; break;
353 }
354 break;
355 }
356
357 case 0x04000000:
358 case 0x06000000:
359 /*
360 * ldr
361 */
362 if ((insn & 0x0010f000) == 0x0010f000) {
363 unsigned long base;
364
365 base = ptrace_getrn(child, insn);
366 if (insn & 1 << 24) {
367 long aluop2;
368
369 if (insn & 0x02000000)
370 aluop2 = ptrace_getldrop2(child, insn);
371 else
372 aluop2 = insn & 0xfff;
373
374 if (insn & 1 << 23)
375 base += aluop2;
376 else
377 base -= aluop2;
378 }
379 read_u32(child, base, &alt);
380 }
381 break;
382
383 case 0x08000000:
384 /*
385 * ldm
386 */
387 if ((insn & 0x00108000) == 0x00108000) {
388 unsigned long base;
389 unsigned int nr_regs;
390
391 if (insn & (1 << 23)) {
392 nr_regs = hweight16(insn & 65535) << 2;
393
394 if (!(insn & (1 << 24)))
395 nr_regs -= 4;
396 } else {
397 if (insn & (1 << 24))
398 nr_regs = -4;
399 else
400 nr_regs = 0;
401 }
402
403 base = ptrace_getrn(child, insn);
404
405 read_u32(child, base + nr_regs, &alt);
406 break;
407 }
408 break;
409
410 case 0x0a000000: {
411 /*
412 * bl or b
413 */
414 signed long displ;
415 /* It's a branch/branch link: instead of trying to
416 * figure out whether the branch will be taken or not,
417 * we'll put a breakpoint at both locations. This is
418 * simpler, more reliable, and probably not a whole lot
419 * slower than the alternative approach of emulating the
420 * branch.
421 */
422 displ = (insn & 0x00ffffff) << 8;
423 displ = (displ >> 6) + 8;
424 if (displ != 0 && displ != 4)
425 alt = pc + displ;
426 }
427 break;
428 }
429
430 return alt;
431}
432
433static int
434swap_insn(struct task_struct *task, unsigned long addr,
435 void *old_insn, void *new_insn, int size)
436{
437 int ret;
438
439 ret = access_process_vm(task, addr, old_insn, size, 0);
440 if (ret == size)
441 ret = access_process_vm(task, addr, new_insn, size, 1);
442 return ret;
443}
444
445static void
446add_breakpoint(struct task_struct *task, struct debug_info *dbg, unsigned long addr)
447{
448 int nr = dbg->nsaved;
449
450 if (nr < 2) {
451 u32 new_insn = BREAKINST_ARM;
452 int res;
453
454 res = swap_insn(task, addr, &dbg->bp[nr].insn, &new_insn, 4);
455
456 if (res == 4) {
457 dbg->bp[nr].address = addr;
458 dbg->nsaved += 1;
459 }
460 } else
461 printk(KERN_ERR "ptrace: too many breakpoints\n");
462}
463
464/*
465 * Clear one breakpoint in the user program. We copy what the hardware
466 * does and use bit 0 of the address to indicate whether this is a Thumb
467 * breakpoint or an ARM breakpoint.
468 */
469static void clear_breakpoint(struct task_struct *task, struct debug_entry *bp)
470{
471 unsigned long addr = bp->address;
472 union debug_insn old_insn;
473 int ret;
474
475 if (addr & 1) {
476 ret = swap_insn(task, addr & ~1, &old_insn.thumb,
477 &bp->insn.thumb, 2);
478
479 if (ret != 2 || old_insn.thumb != BREAKINST_THUMB)
480 printk(KERN_ERR "%s:%d: corrupted Thumb breakpoint at "
481 "0x%08lx (0x%04x)\n", task->comm,
482 task_pid_nr(task), addr, old_insn.thumb);
483 } else {
484 ret = swap_insn(task, addr & ~3, &old_insn.arm,
485 &bp->insn.arm, 4);
486
487 if (ret != 4 || old_insn.arm != BREAKINST_ARM)
488 printk(KERN_ERR "%s:%d: corrupted ARM breakpoint at "
489 "0x%08lx (0x%08x)\n", task->comm,
490 task_pid_nr(task), addr, old_insn.arm);
491 }
492}
493
494void ptrace_set_bpt(struct task_struct *child)
495{
496 struct pt_regs *regs;
497 unsigned long pc;
498 u32 insn;
499 int res;
500
501 regs = task_pt_regs(child);
502 pc = instruction_pointer(regs);
503
504 if (thumb_mode(regs)) {
505 printk(KERN_WARNING "ptrace: can't handle thumb mode\n");
506 return;
507 }
508
509 res = read_instr(child, pc, &insn);
510 if (!res) {
511 struct debug_info *dbg = &child->thread.debug;
512 unsigned long alt;
513
514 dbg->nsaved = 0;
515
516 alt = get_branch_address(child, pc, insn);
517 if (alt)
518 add_breakpoint(child, dbg, alt);
519
520 /*
521 * Note that we ignore the result of setting the above
522 * breakpoint since it may fail. When it does, this is
523 * not so much an error, but a forewarning that we may
524 * be receiving a prefetch abort shortly.
525 *
526 * If we don't set this breakpoint here, then we can
527 * lose control of the thread during single stepping.
528 */
529 if (!alt || predicate(insn) != PREDICATE_ALWAYS)
530 add_breakpoint(child, dbg, pc + 4);
531 }
532}
533
534/*
535 * Ensure no single-step breakpoint is pending. Returns non-zero
536 * value if child was being single-stepped.
537 */
538void ptrace_cancel_bpt(struct task_struct *child)
539{
540 int i, nsaved = child->thread.debug.nsaved;
541
542 child->thread.debug.nsaved = 0;
543
544 if (nsaved > 2) {
545 printk("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved);
546 nsaved = 2;
547 }
548
549 for (i = 0; i < nsaved; i++)
550 clear_breakpoint(child, &child->thread.debug.bp[i]);
551}
552
553void user_disable_single_step(struct task_struct *task)
554{
555 task->ptrace &= ~PT_SINGLESTEP;
556 ptrace_cancel_bpt(task);
557}
558
559void user_enable_single_step(struct task_struct *task)
560{
561 task->ptrace |= PT_SINGLESTEP;
562}
563
564/* 185/*
565 * Called by kernel/ptrace.c when detaching.. 186 * Called by kernel/ptrace.c when detaching..
566 */ 187 */
567void ptrace_disable(struct task_struct *child) 188void ptrace_disable(struct task_struct *child)
568{ 189{
569 user_disable_single_step(child); 190 /* Nothing to do. */
570} 191}
571 192
572/* 193/*
@@ -576,8 +197,6 @@ void ptrace_break(struct task_struct *tsk, struct pt_regs *regs)
576{ 197{
577 siginfo_t info; 198 siginfo_t info;
578 199
579 ptrace_cancel_bpt(tsk);
580
581 info.si_signo = SIGTRAP; 200 info.si_signo = SIGTRAP;
582 info.si_errno = 0; 201 info.si_errno = 0;
583 info.si_code = TRAP_BRKPT; 202 info.si_code = TRAP_BRKPT;
diff --git a/arch/arm/kernel/ptrace.h b/arch/arm/kernel/ptrace.h
deleted file mode 100644
index 3926605b82e..00000000000
--- a/arch/arm/kernel/ptrace.h
+++ /dev/null
@@ -1,37 +0,0 @@
1/*
2 * linux/arch/arm/kernel/ptrace.h
3 *
4 * Copyright (C) 2000-2003 Russell King
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10#include <linux/ptrace.h>
11
12extern void ptrace_cancel_bpt(struct task_struct *);
13extern void ptrace_set_bpt(struct task_struct *);
14extern void ptrace_break(struct task_struct *, struct pt_regs *);
15
16/*
17 * Send SIGTRAP if we're single-stepping
18 */
19static inline void single_step_trap(struct task_struct *task)
20{
21 if (task->ptrace & PT_SINGLESTEP) {
22 ptrace_cancel_bpt(task);
23 send_sig(SIGTRAP, task, 1);
24 }
25}
26
27static inline void single_step_clear(struct task_struct *task)
28{
29 if (task->ptrace & PT_SINGLESTEP)
30 ptrace_cancel_bpt(task);
31}
32
33static inline void single_step_set(struct task_struct *task)
34{
35 if (task->ptrace & PT_SINGLESTEP)
36 ptrace_set_bpt(task);
37}
diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c
index 907d5a620bc..7709668c484 100644
--- a/arch/arm/kernel/signal.c
+++ b/arch/arm/kernel/signal.c
@@ -20,7 +20,6 @@
20#include <asm/unistd.h> 20#include <asm/unistd.h>
21#include <asm/vfp.h> 21#include <asm/vfp.h>
22 22
23#include "ptrace.h"
24#include "signal.h" 23#include "signal.h"
25 24
26#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) 25#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP)))
@@ -348,8 +347,6 @@ asmlinkage int sys_sigreturn(struct pt_regs *regs)
348 if (restore_sigframe(regs, frame)) 347 if (restore_sigframe(regs, frame))
349 goto badframe; 348 goto badframe;
350 349
351 single_step_trap(current);
352
353 return regs->ARM_r0; 350 return regs->ARM_r0;
354 351
355badframe: 352badframe:
@@ -383,8 +380,6 @@ asmlinkage int sys_rt_sigreturn(struct pt_regs *regs)
383 if (do_sigaltstack(&frame->sig.uc.uc_stack, NULL, regs->ARM_sp) == -EFAULT) 380 if (do_sigaltstack(&frame->sig.uc.uc_stack, NULL, regs->ARM_sp) == -EFAULT)
384 goto badframe; 381 goto badframe;
385 382
386 single_step_trap(current);
387
388 return regs->ARM_r0; 383 return regs->ARM_r0;
389 384
390badframe: 385badframe:
@@ -704,8 +699,6 @@ static void do_signal(struct pt_regs *regs, int syscall)
704 if (try_to_freeze()) 699 if (try_to_freeze())
705 goto no_signal; 700 goto no_signal;
706 701
707 single_step_clear(current);
708
709 signr = get_signal_to_deliver(&info, &ka, regs, NULL); 702 signr = get_signal_to_deliver(&info, &ka, regs, NULL);
710 if (signr > 0) { 703 if (signr > 0) {
711 sigset_t *oldset; 704 sigset_t *oldset;
@@ -724,7 +717,6 @@ static void do_signal(struct pt_regs *regs, int syscall)
724 if (test_thread_flag(TIF_RESTORE_SIGMASK)) 717 if (test_thread_flag(TIF_RESTORE_SIGMASK))
725 clear_thread_flag(TIF_RESTORE_SIGMASK); 718 clear_thread_flag(TIF_RESTORE_SIGMASK);
726 } 719 }
727 single_step_set(current);
728 return; 720 return;
729 } 721 }
730 722
@@ -770,7 +762,6 @@ static void do_signal(struct pt_regs *regs, int syscall)
770 sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL); 762 sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL);
771 } 763 }
772 } 764 }
773 single_step_set(current);
774} 765}
775 766
776asmlinkage void 767asmlinkage void
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c
index 7f53c3651c5..21ac43f1c2d 100644
--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c
@@ -23,6 +23,7 @@
23#include <linux/kexec.h> 23#include <linux/kexec.h>
24#include <linux/delay.h> 24#include <linux/delay.h>
25#include <linux/init.h> 25#include <linux/init.h>
26#include <linux/sched.h>
26 27
27#include <asm/atomic.h> 28#include <asm/atomic.h>
28#include <asm/cacheflush.h> 29#include <asm/cacheflush.h>
@@ -32,7 +33,6 @@
32#include <asm/unwind.h> 33#include <asm/unwind.h>
33#include <asm/tls.h> 34#include <asm/tls.h>
34 35
35#include "ptrace.h"
36#include "signal.h" 36#include "signal.h"
37 37
38static const char *handler[]= { "prefetch abort", "data abort", "address exception", "interrupt" }; 38static const char *handler[]= { "prefetch abort", "data abort", "address exception", "interrupt" };