diff options
| -rw-r--r-- | arch/x86/kernel/ptrace.c | 57 | ||||
| -rw-r--r-- | include/linux/hw_breakpoint.h | 4 | ||||
| -rw-r--r-- | include/linux/perf_event.h | 5 | ||||
| -rw-r--r-- | kernel/hw_breakpoint.c | 42 | ||||
| -rw-r--r-- | kernel/perf_event.c | 4 |
5 files changed, 66 insertions, 46 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 | ||
| 598 | static struct perf_event * | 598 | static int |
| 599 | ptrace_modify_breakpoint(struct perf_event *bp, int len, int type, | 599 | ptrace_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 | } |
diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h index 42da1ce19ec0..69f07a9f1277 100644 --- a/include/linux/hw_breakpoint.h +++ b/include/linux/hw_breakpoint.h | |||
| @@ -55,7 +55,7 @@ register_user_hw_breakpoint(struct perf_event_attr *attr, | |||
| 55 | struct task_struct *tsk); | 55 | struct task_struct *tsk); |
| 56 | 56 | ||
| 57 | /* FIXME: only change from the attr, and don't unregister */ | 57 | /* FIXME: only change from the attr, and don't unregister */ |
| 58 | extern struct perf_event * | 58 | extern int |
| 59 | modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr); | 59 | modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr); |
| 60 | 60 | ||
| 61 | /* | 61 | /* |
| @@ -91,7 +91,7 @@ static inline struct perf_event * | |||
| 91 | register_user_hw_breakpoint(struct perf_event_attr *attr, | 91 | register_user_hw_breakpoint(struct perf_event_attr *attr, |
| 92 | perf_overflow_handler_t triggered, | 92 | perf_overflow_handler_t triggered, |
| 93 | struct task_struct *tsk) { return NULL; } | 93 | struct task_struct *tsk) { return NULL; } |
| 94 | static inline struct perf_event * | 94 | static inline int |
| 95 | modify_user_hw_breakpoint(struct perf_event *bp, | 95 | modify_user_hw_breakpoint(struct perf_event *bp, |
| 96 | struct perf_event_attr *attr) { return NULL; } | 96 | struct perf_event_attr *attr) { return NULL; } |
| 97 | static inline struct perf_event * | 97 | static inline struct perf_event * |
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index bf3329413e18..64a53f74c9a9 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h | |||
| @@ -872,6 +872,8 @@ extern void perf_output_copy(struct perf_output_handle *handle, | |||
| 872 | const void *buf, unsigned int len); | 872 | const void *buf, unsigned int len); |
| 873 | extern int perf_swevent_get_recursion_context(void); | 873 | extern int perf_swevent_get_recursion_context(void); |
| 874 | extern void perf_swevent_put_recursion_context(int rctx); | 874 | extern void perf_swevent_put_recursion_context(int rctx); |
| 875 | extern void perf_event_enable(struct perf_event *event); | ||
| 876 | extern void perf_event_disable(struct perf_event *event); | ||
| 875 | #else | 877 | #else |
| 876 | static inline void | 878 | static inline void |
| 877 | perf_event_task_sched_in(struct task_struct *task, int cpu) { } | 879 | perf_event_task_sched_in(struct task_struct *task, int cpu) { } |
| @@ -902,7 +904,8 @@ static inline void perf_event_fork(struct task_struct *tsk) { } | |||
| 902 | static inline void perf_event_init(void) { } | 904 | static inline void perf_event_init(void) { } |
| 903 | static inline int perf_swevent_get_recursion_context(void) { return -1; } | 905 | static inline int perf_swevent_get_recursion_context(void) { return -1; } |
| 904 | static inline void perf_swevent_put_recursion_context(int rctx) { } | 906 | static inline void perf_swevent_put_recursion_context(int rctx) { } |
| 905 | 907 | static inline void perf_event_enable(struct perf_event *event) { } | |
| 908 | static inline void perf_event_disable(struct perf_event *event) { } | ||
| 906 | #endif | 909 | #endif |
| 907 | 910 | ||
| 908 | #define perf_output_put(handle, x) \ | 911 | #define perf_output_put(handle, x) \ |
diff --git a/kernel/hw_breakpoint.c b/kernel/hw_breakpoint.c index 03a0773ac2b2..366eedf949c0 100644 --- a/kernel/hw_breakpoint.c +++ b/kernel/hw_breakpoint.c | |||
| @@ -320,18 +320,40 @@ EXPORT_SYMBOL_GPL(register_user_hw_breakpoint); | |||
| 320 | * @triggered: callback to trigger when we hit the breakpoint | 320 | * @triggered: callback to trigger when we hit the breakpoint |
| 321 | * @tsk: pointer to 'task_struct' of the process to which the address belongs | 321 | * @tsk: pointer to 'task_struct' of the process to which the address belongs |
| 322 | */ | 322 | */ |
| 323 | struct perf_event * | 323 | int modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr) |
| 324 | modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr) | ||
| 325 | { | 324 | { |
| 326 | /* | 325 | u64 old_addr = bp->attr.bp_addr; |
| 327 | * FIXME: do it without unregistering | 326 | int old_type = bp->attr.bp_type; |
| 328 | * - We don't want to lose our slot | 327 | int old_len = bp->attr.bp_len; |
| 329 | * - If the new bp is incorrect, don't lose the older one | 328 | int err = 0; |
| 330 | */ | 329 | |
| 331 | unregister_hw_breakpoint(bp); | 330 | perf_event_disable(bp); |
| 331 | |||
| 332 | bp->attr.bp_addr = attr->bp_addr; | ||
| 333 | bp->attr.bp_type = attr->bp_type; | ||
| 334 | bp->attr.bp_len = attr->bp_len; | ||
| 335 | |||
| 336 | if (attr->disabled) | ||
| 337 | goto end; | ||
| 332 | 338 | ||
| 333 | return perf_event_create_kernel_counter(attr, -1, bp->ctx->task->pid, | 339 | err = arch_validate_hwbkpt_settings(bp, bp->ctx->task); |
| 334 | bp->overflow_handler); | 340 | if (!err) |
| 341 | perf_event_enable(bp); | ||
| 342 | |||
| 343 | if (err) { | ||
| 344 | bp->attr.bp_addr = old_addr; | ||
| 345 | bp->attr.bp_type = old_type; | ||
| 346 | bp->attr.bp_len = old_len; | ||
| 347 | if (!bp->attr.disabled) | ||
| 348 | perf_event_enable(bp); | ||
| 349 | |||
| 350 | return err; | ||
| 351 | } | ||
| 352 | |||
| 353 | end: | ||
| 354 | bp->attr.disabled = attr->disabled; | ||
| 355 | |||
| 356 | return 0; | ||
| 335 | } | 357 | } |
| 336 | EXPORT_SYMBOL_GPL(modify_user_hw_breakpoint); | 358 | EXPORT_SYMBOL_GPL(modify_user_hw_breakpoint); |
| 337 | 359 | ||
diff --git a/kernel/perf_event.c b/kernel/perf_event.c index fd43ff4ac860..3b0cf86eee84 100644 --- a/kernel/perf_event.c +++ b/kernel/perf_event.c | |||
| @@ -567,7 +567,7 @@ static void __perf_event_disable(void *info) | |||
| 567 | * is the current context on this CPU and preemption is disabled, | 567 | * is the current context on this CPU and preemption is disabled, |
| 568 | * hence we can't get into perf_event_task_sched_out for this context. | 568 | * hence we can't get into perf_event_task_sched_out for this context. |
| 569 | */ | 569 | */ |
| 570 | static void perf_event_disable(struct perf_event *event) | 570 | void perf_event_disable(struct perf_event *event) |
| 571 | { | 571 | { |
| 572 | struct perf_event_context *ctx = event->ctx; | 572 | struct perf_event_context *ctx = event->ctx; |
| 573 | struct task_struct *task = ctx->task; | 573 | struct task_struct *task = ctx->task; |
| @@ -971,7 +971,7 @@ static void __perf_event_enable(void *info) | |||
| 971 | * perf_event_for_each_child or perf_event_for_each as described | 971 | * perf_event_for_each_child or perf_event_for_each as described |
| 972 | * for perf_event_disable. | 972 | * for perf_event_disable. |
| 973 | */ | 973 | */ |
| 974 | static void perf_event_enable(struct perf_event *event) | 974 | void perf_event_enable(struct perf_event *event) |
| 975 | { | 975 | { |
| 976 | struct perf_event_context *ctx = event->ctx; | 976 | struct perf_event_context *ctx = event->ctx; |
| 977 | struct task_struct *task = ctx->task; | 977 | struct task_struct *task = ctx->task; |
