aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/ptrace.c
diff options
context:
space:
mode:
authorFrederic Weisbecker <fweisbec@gmail.com>2009-12-09 03:25:48 -0500
committerIngo Molnar <mingo@elte.hu>2009-12-09 03:48:20 -0500
commit44234adcdce38f83c56e05f808ce656175b4beeb (patch)
treecaff2ca7bbf4bf7c0b12652caf739bcc6db5f4d3 /arch/x86/kernel/ptrace.c
parentc937fe20cb6d9e24c6ad5f9f0c64d64c78411057 (diff)
hw-breakpoints: Modify breakpoints without unregistering them
Currently, when ptrace needs to modify a breakpoint, like disabling it, changing its address, type or len, it calls modify_user_hw_breakpoint(). This latter will perform the heavy and racy task of unregistering the old breakpoint and registering a new one. This is racy as someone else might steal the reserved breakpoint slot under us, which is undesired as the breakpoint is only supposed to be modified, sometimes in the middle of a debugging workflow. We don't want our slot to be stolen in the middle. So instead of unregistering/registering the breakpoint, just disable it while we modify its breakpoint fields and re-enable it after if necessary. Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Prasad <prasad@linux.vnet.ibm.com> LKML-Reference: <1260347148-5519-1-git-send-regression-fweisbec@gmail.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch/x86/kernel/ptrace.c')
-rw-r--r--arch/x86/kernel/ptrace.c57
1 files changed, 26 insertions, 31 deletions
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index b361d28061d0..7079ddaf0731 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -595,7 +595,7 @@ static unsigned long ptrace_get_dr7(struct perf_event *bp[])
595 return dr7; 595 return dr7;
596} 596}
597 597
598static struct perf_event * 598static int
599ptrace_modify_breakpoint(struct perf_event *bp, int len, int type, 599ptrace_modify_breakpoint(struct perf_event *bp, int len, int type,
600 struct task_struct *tsk, int disabled) 600 struct task_struct *tsk, int disabled)
601{ 601{
@@ -609,11 +609,11 @@ ptrace_modify_breakpoint(struct perf_event *bp, int len, int type,
609 * written the address register first 609 * written the address register first
610 */ 610 */
611 if (!bp) 611 if (!bp)
612 return ERR_PTR(-EINVAL); 612 return -EINVAL;
613 613
614 err = arch_bp_generic_fields(len, type, &gen_len, &gen_type); 614 err = arch_bp_generic_fields(len, type, &gen_len, &gen_type);
615 if (err) 615 if (err)
616 return ERR_PTR(err); 616 return err;
617 617
618 attr = bp->attr; 618 attr = bp->attr;
619 attr.bp_len = gen_len; 619 attr.bp_len = gen_len;
@@ -658,28 +658,17 @@ restore:
658 if (!second_pass) 658 if (!second_pass)
659 continue; 659 continue;
660 660
661 thread->ptrace_bps[i] = NULL; 661 rc = ptrace_modify_breakpoint(bp, len, type,
662 bp = ptrace_modify_breakpoint(bp, len, type,
663 tsk, 1); 662 tsk, 1);
664 if (IS_ERR(bp)) { 663 if (rc)
665 rc = PTR_ERR(bp);
666 thread->ptrace_bps[i] = NULL;
667 break; 664 break;
668 }
669 thread->ptrace_bps[i] = bp;
670 } 665 }
671 continue; 666 continue;
672 } 667 }
673 668
674 bp = ptrace_modify_breakpoint(bp, len, type, tsk, 0); 669 rc = ptrace_modify_breakpoint(bp, len, type, tsk, 0);
675 670 if (rc)
676 /* Incorrect bp, or we have a bug in bp API */
677 if (IS_ERR(bp)) {
678 rc = PTR_ERR(bp);
679 thread->ptrace_bps[i] = NULL;
680 break; 671 break;
681 }
682 thread->ptrace_bps[i] = bp;
683 } 672 }
684 /* 673 /*
685 * Make a second pass to free the remaining unused breakpoints 674 * Make a second pass to free the remaining unused breakpoints
@@ -737,26 +726,32 @@ static int ptrace_set_breakpoint_addr(struct task_struct *tsk, int nr,
737 attr.disabled = 1; 726 attr.disabled = 1;
738 727
739 bp = register_user_hw_breakpoint(&attr, ptrace_triggered, tsk); 728 bp = register_user_hw_breakpoint(&attr, ptrace_triggered, tsk);
729
730 /*
731 * CHECKME: the previous code returned -EIO if the addr wasn't
732 * a valid task virtual addr. The new one will return -EINVAL in
733 * this case.
734 * -EINVAL may be what we want for in-kernel breakpoints users,
735 * but -EIO looks better for ptrace, since we refuse a register
736 * writing for the user. And anyway this is the previous
737 * behaviour.
738 */
739 if (IS_ERR(bp))
740 return PTR_ERR(bp);
741
742 t->ptrace_bps[nr] = bp;
740 } else { 743 } else {
744 int err;
745
741 bp = t->ptrace_bps[nr]; 746 bp = t->ptrace_bps[nr];
742 t->ptrace_bps[nr] = NULL;
743 747
744 attr = bp->attr; 748 attr = bp->attr;
745 attr.bp_addr = addr; 749 attr.bp_addr = addr;
746 bp = modify_user_hw_breakpoint(bp, &attr); 750 err = modify_user_hw_breakpoint(bp, &attr);
751 if (err)
752 return err;
747 } 753 }
748 /*
749 * CHECKME: the previous code returned -EIO if the addr wasn't a
750 * valid task virtual addr. The new one will return -EINVAL in this
751 * case.
752 * -EINVAL may be what we want for in-kernel breakpoints users, but
753 * -EIO looks better for ptrace, since we refuse a register writing
754 * for the user. And anyway this is the previous behaviour.
755 */
756 if (IS_ERR(bp))
757 return PTR_ERR(bp);
758 754
759 t->ptrace_bps[nr] = bp;
760 755
761 return 0; 756 return 0;
762} 757}