diff options
-rw-r--r-- | arch/x86/kernel/ptrace.c | 74 | ||||
-rw-r--r-- | include/linux/hw_breakpoint.h | 36 | ||||
-rw-r--r-- | kernel/hw_breakpoint.c | 87 |
3 files changed, 75 insertions, 122 deletions
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c index 75e0cd847bd6..2941b32ea666 100644 --- a/arch/x86/kernel/ptrace.c +++ b/arch/x86/kernel/ptrace.c | |||
@@ -593,6 +593,34 @@ static unsigned long ptrace_get_dr7(struct perf_event *bp[]) | |||
593 | return dr7; | 593 | return dr7; |
594 | } | 594 | } |
595 | 595 | ||
596 | static struct perf_event * | ||
597 | ptrace_modify_breakpoint(struct perf_event *bp, int len, int type, | ||
598 | struct task_struct *tsk) | ||
599 | { | ||
600 | int err; | ||
601 | int gen_len, gen_type; | ||
602 | DEFINE_BREAKPOINT_ATTR(attr); | ||
603 | |||
604 | /* | ||
605 | * We shoud have at least an inactive breakpoint at this | ||
606 | * slot. It means the user is writing dr7 without having | ||
607 | * written the address register first | ||
608 | */ | ||
609 | if (!bp) | ||
610 | return ERR_PTR(-EINVAL); | ||
611 | |||
612 | err = arch_bp_generic_fields(len, type, &gen_len, &gen_type); | ||
613 | if (err) | ||
614 | return ERR_PTR(err); | ||
615 | |||
616 | attr = bp->attr; | ||
617 | attr.bp_len = gen_len; | ||
618 | attr.bp_type = gen_type; | ||
619 | attr.disabled = 0; | ||
620 | |||
621 | return modify_user_hw_breakpoint(bp, &attr, bp->callback, tsk); | ||
622 | } | ||
623 | |||
596 | /* | 624 | /* |
597 | * Handle ptrace writes to debug register 7. | 625 | * Handle ptrace writes to debug register 7. |
598 | */ | 626 | */ |
@@ -603,7 +631,6 @@ static int ptrace_write_dr7(struct task_struct *tsk, unsigned long data) | |||
603 | int i, orig_ret = 0, rc = 0; | 631 | int i, orig_ret = 0, rc = 0; |
604 | int enabled, second_pass = 0; | 632 | int enabled, second_pass = 0; |
605 | unsigned len, type; | 633 | unsigned len, type; |
606 | int gen_len, gen_type; | ||
607 | struct perf_event *bp; | 634 | struct perf_event *bp; |
608 | 635 | ||
609 | data &= ~DR_CONTROL_RESERVED; | 636 | data &= ~DR_CONTROL_RESERVED; |
@@ -634,33 +661,12 @@ restore: | |||
634 | continue; | 661 | continue; |
635 | } | 662 | } |
636 | 663 | ||
637 | /* | 664 | bp = ptrace_modify_breakpoint(bp, len, type, tsk); |
638 | * We shoud have at least an inactive breakpoint at this | ||
639 | * slot. It means the user is writing dr7 without having | ||
640 | * written the address register first | ||
641 | */ | ||
642 | if (!bp) { | ||
643 | rc = -EINVAL; | ||
644 | break; | ||
645 | } | ||
646 | |||
647 | rc = arch_bp_generic_fields(len, type, &gen_len, &gen_type); | ||
648 | if (rc) | ||
649 | break; | ||
650 | |||
651 | /* | ||
652 | * This is a temporary thing as bp is unregistered/registered | ||
653 | * to simulate modification | ||
654 | */ | ||
655 | bp = modify_user_hw_breakpoint(bp, bp->attr.bp_addr, gen_len, | ||
656 | gen_type, bp->callback, | ||
657 | tsk, true); | ||
658 | thread->ptrace_bps[i] = NULL; | ||
659 | 665 | ||
660 | /* Incorrect bp, or we have a bug in bp API */ | 666 | /* Incorrect bp, or we have a bug in bp API */ |
661 | if (IS_ERR(bp)) { | 667 | if (IS_ERR(bp)) { |
662 | rc = PTR_ERR(bp); | 668 | rc = PTR_ERR(bp); |
663 | bp = NULL; | 669 | thread->ptrace_bps[i] = NULL; |
664 | break; | 670 | break; |
665 | } | 671 | } |
666 | thread->ptrace_bps[i] = bp; | 672 | thread->ptrace_bps[i] = bp; |
@@ -707,24 +713,26 @@ static int ptrace_set_breakpoint_addr(struct task_struct *tsk, int nr, | |||
707 | { | 713 | { |
708 | struct perf_event *bp; | 714 | struct perf_event *bp; |
709 | struct thread_struct *t = &tsk->thread; | 715 | struct thread_struct *t = &tsk->thread; |
716 | DEFINE_BREAKPOINT_ATTR(attr); | ||
710 | 717 | ||
711 | if (!t->ptrace_bps[nr]) { | 718 | if (!t->ptrace_bps[nr]) { |
712 | /* | 719 | /* |
713 | * Put stub len and type to register (reserve) an inactive but | 720 | * Put stub len and type to register (reserve) an inactive but |
714 | * correct bp | 721 | * correct bp |
715 | */ | 722 | */ |
716 | bp = register_user_hw_breakpoint(addr, HW_BREAKPOINT_LEN_1, | 723 | attr.bp_addr = addr; |
717 | HW_BREAKPOINT_W, | 724 | attr.bp_len = HW_BREAKPOINT_LEN_1; |
718 | ptrace_triggered, tsk, | 725 | attr.bp_type = HW_BREAKPOINT_W; |
719 | false); | 726 | attr.disabled = 1; |
727 | |||
728 | bp = register_user_hw_breakpoint(&attr, ptrace_triggered, tsk); | ||
720 | } else { | 729 | } else { |
721 | bp = t->ptrace_bps[nr]; | 730 | bp = t->ptrace_bps[nr]; |
722 | t->ptrace_bps[nr] = NULL; | 731 | t->ptrace_bps[nr] = NULL; |
723 | bp = modify_user_hw_breakpoint(bp, addr, bp->attr.bp_len, | 732 | |
724 | bp->attr.bp_type, | 733 | attr = bp->attr; |
725 | bp->callback, | 734 | attr.bp_addr = addr; |
726 | tsk, | 735 | bp = modify_user_hw_breakpoint(bp, &attr, bp->callback, tsk); |
727 | bp->attr.disabled); | ||
728 | } | 736 | } |
729 | /* | 737 | /* |
730 | * CHECKME: the previous code returned -EIO if the addr wasn't a | 738 | * CHECKME: the previous code returned -EIO if the addr wasn't a |
diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h index c9f7f7c7b0e0..5da472e434b7 100644 --- a/include/linux/hw_breakpoint.h +++ b/include/linux/hw_breakpoint.h | |||
@@ -20,6 +20,14 @@ enum { | |||
20 | 20 | ||
21 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | 21 | #ifdef CONFIG_HAVE_HW_BREAKPOINT |
22 | 22 | ||
23 | /* As it's for in-kernel or ptrace use, we want it to be pinned */ | ||
24 | #define DEFINE_BREAKPOINT_ATTR(name) \ | ||
25 | struct perf_event_attr name = { \ | ||
26 | .type = PERF_TYPE_BREAKPOINT, \ | ||
27 | .size = sizeof(name), \ | ||
28 | .pinned = 1, \ | ||
29 | }; | ||
30 | |||
23 | static inline unsigned long hw_breakpoint_addr(struct perf_event *bp) | 31 | static inline unsigned long hw_breakpoint_addr(struct perf_event *bp) |
24 | { | 32 | { |
25 | return bp->attr.bp_addr; | 33 | return bp->attr.bp_addr; |
@@ -36,22 +44,16 @@ static inline int hw_breakpoint_len(struct perf_event *bp) | |||
36 | } | 44 | } |
37 | 45 | ||
38 | extern struct perf_event * | 46 | extern struct perf_event * |
39 | register_user_hw_breakpoint(unsigned long addr, | 47 | register_user_hw_breakpoint(struct perf_event_attr *attr, |
40 | int len, | ||
41 | int type, | ||
42 | perf_callback_t triggered, | 48 | perf_callback_t triggered, |
43 | struct task_struct *tsk, | 49 | struct task_struct *tsk); |
44 | bool active); | ||
45 | 50 | ||
46 | /* FIXME: only change from the attr, and don't unregister */ | 51 | /* FIXME: only change from the attr, and don't unregister */ |
47 | extern struct perf_event * | 52 | extern struct perf_event * |
48 | modify_user_hw_breakpoint(struct perf_event *bp, | 53 | modify_user_hw_breakpoint(struct perf_event *bp, |
49 | unsigned long addr, | 54 | struct perf_event_attr *attr, |
50 | int len, | ||
51 | int type, | ||
52 | perf_callback_t triggered, | 55 | perf_callback_t triggered, |
53 | struct task_struct *tsk, | 56 | struct task_struct *tsk); |
54 | bool active); | ||
55 | 57 | ||
56 | /* | 58 | /* |
57 | * Kernel breakpoints are not associated with any particular thread. | 59 | * Kernel breakpoints are not associated with any particular thread. |
@@ -89,20 +91,14 @@ static inline struct arch_hw_breakpoint *counter_arch_bp(struct perf_event *bp) | |||
89 | #else /* !CONFIG_HAVE_HW_BREAKPOINT */ | 91 | #else /* !CONFIG_HAVE_HW_BREAKPOINT */ |
90 | 92 | ||
91 | static inline struct perf_event * | 93 | static inline struct perf_event * |
92 | register_user_hw_breakpoint(unsigned long addr, | 94 | register_user_hw_breakpoint(struct perf_event_attr *attr, |
93 | int len, | ||
94 | int type, | ||
95 | perf_callback_t triggered, | 95 | perf_callback_t triggered, |
96 | struct task_struct *tsk, | 96 | struct task_struct *tsk) { return NULL; } |
97 | bool active) { return NULL; } | ||
98 | static inline struct perf_event * | 97 | static inline struct perf_event * |
99 | modify_user_hw_breakpoint(struct perf_event *bp, | 98 | modify_user_hw_breakpoint(struct perf_event *bp, |
100 | unsigned long addr, | 99 | struct perf_event_attr *attr, |
101 | int len, | ||
102 | int type, | ||
103 | perf_callback_t triggered, | 100 | perf_callback_t triggered, |
104 | struct task_struct *tsk, | 101 | struct task_struct *tsk) { return NULL; } |
105 | bool active) { return NULL; } | ||
106 | static inline struct perf_event * | 102 | static inline struct perf_event * |
107 | register_wide_hw_breakpoint_cpu(unsigned long addr, | 103 | register_wide_hw_breakpoint_cpu(unsigned long addr, |
108 | int len, | 104 | int len, |
diff --git a/kernel/hw_breakpoint.c b/kernel/hw_breakpoint.c index 32e1018191be..2a47514f12fd 100644 --- a/kernel/hw_breakpoint.c +++ b/kernel/hw_breakpoint.c | |||
@@ -289,90 +289,32 @@ int register_perf_hw_breakpoint(struct perf_event *bp) | |||
289 | return __register_perf_hw_breakpoint(bp); | 289 | return __register_perf_hw_breakpoint(bp); |
290 | } | 290 | } |
291 | 291 | ||
292 | /* | ||
293 | * Register a breakpoint bound to a task and a given cpu. | ||
294 | * If cpu is -1, the breakpoint is active for the task in every cpu | ||
295 | * If the task is -1, the breakpoint is active for every tasks in the given | ||
296 | * cpu. | ||
297 | */ | ||
298 | static struct perf_event * | ||
299 | register_user_hw_breakpoint_cpu(unsigned long addr, | ||
300 | int len, | ||
301 | int type, | ||
302 | perf_callback_t triggered, | ||
303 | pid_t pid, | ||
304 | int cpu, | ||
305 | bool active) | ||
306 | { | ||
307 | struct perf_event_attr *attr; | ||
308 | struct perf_event *bp; | ||
309 | |||
310 | attr = kzalloc(sizeof(*attr), GFP_KERNEL); | ||
311 | if (!attr) | ||
312 | return ERR_PTR(-ENOMEM); | ||
313 | |||
314 | attr->type = PERF_TYPE_BREAKPOINT; | ||
315 | attr->size = sizeof(*attr); | ||
316 | attr->bp_addr = addr; | ||
317 | attr->bp_len = len; | ||
318 | attr->bp_type = type; | ||
319 | /* | ||
320 | * Such breakpoints are used by debuggers to trigger signals when | ||
321 | * we hit the excepted memory op. We can't miss such events, they | ||
322 | * must be pinned. | ||
323 | */ | ||
324 | attr->pinned = 1; | ||
325 | |||
326 | if (!active) | ||
327 | attr->disabled = 1; | ||
328 | |||
329 | bp = perf_event_create_kernel_counter(attr, cpu, pid, triggered); | ||
330 | kfree(attr); | ||
331 | |||
332 | return bp; | ||
333 | } | ||
334 | |||
335 | /** | 292 | /** |
336 | * register_user_hw_breakpoint - register a hardware breakpoint for user space | 293 | * register_user_hw_breakpoint - register a hardware breakpoint for user space |
337 | * @addr: is the memory address that triggers the breakpoint | 294 | * @attr: breakpoint attributes |
338 | * @len: the length of the access to the memory (1 byte, 2 bytes etc...) | ||
339 | * @type: the type of the access to the memory (read/write/exec) | ||
340 | * @triggered: callback to trigger when we hit the breakpoint | 295 | * @triggered: callback to trigger when we hit the breakpoint |
341 | * @tsk: pointer to 'task_struct' of the process to which the address belongs | 296 | * @tsk: pointer to 'task_struct' of the process to which the address belongs |
342 | * @active: should we activate it while registering it | ||
343 | * | ||
344 | */ | 297 | */ |
345 | struct perf_event * | 298 | struct perf_event * |
346 | register_user_hw_breakpoint(unsigned long addr, | 299 | register_user_hw_breakpoint(struct perf_event_attr *attr, |
347 | int len, | ||
348 | int type, | ||
349 | perf_callback_t triggered, | 300 | perf_callback_t triggered, |
350 | struct task_struct *tsk, | 301 | struct task_struct *tsk) |
351 | bool active) | ||
352 | { | 302 | { |
353 | return register_user_hw_breakpoint_cpu(addr, len, type, triggered, | 303 | return perf_event_create_kernel_counter(attr, -1, tsk->pid, triggered); |
354 | tsk->pid, -1, active); | ||
355 | } | 304 | } |
356 | EXPORT_SYMBOL_GPL(register_user_hw_breakpoint); | 305 | EXPORT_SYMBOL_GPL(register_user_hw_breakpoint); |
357 | 306 | ||
358 | /** | 307 | /** |
359 | * modify_user_hw_breakpoint - modify a user-space hardware breakpoint | 308 | * modify_user_hw_breakpoint - modify a user-space hardware breakpoint |
360 | * @bp: the breakpoint structure to modify | 309 | * @bp: the breakpoint structure to modify |
361 | * @addr: is the memory address that triggers the breakpoint | 310 | * @attr: new breakpoint attributes |
362 | * @len: the length of the access to the memory (1 byte, 2 bytes etc...) | ||
363 | * @type: the type of the access to the memory (read/write/exec) | ||
364 | * @triggered: callback to trigger when we hit the breakpoint | 311 | * @triggered: callback to trigger when we hit the breakpoint |
365 | * @tsk: pointer to 'task_struct' of the process to which the address belongs | 312 | * @tsk: pointer to 'task_struct' of the process to which the address belongs |
366 | * @active: should we activate it while registering it | ||
367 | */ | 313 | */ |
368 | struct perf_event * | 314 | struct perf_event * |
369 | modify_user_hw_breakpoint(struct perf_event *bp, | 315 | modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr, |
370 | unsigned long addr, | ||
371 | int len, | ||
372 | int type, | ||
373 | perf_callback_t triggered, | 316 | perf_callback_t triggered, |
374 | struct task_struct *tsk, | 317 | struct task_struct *tsk) |
375 | bool active) | ||
376 | { | 318 | { |
377 | /* | 319 | /* |
378 | * FIXME: do it without unregistering | 320 | * FIXME: do it without unregistering |
@@ -381,8 +323,7 @@ modify_user_hw_breakpoint(struct perf_event *bp, | |||
381 | */ | 323 | */ |
382 | unregister_hw_breakpoint(bp); | 324 | unregister_hw_breakpoint(bp); |
383 | 325 | ||
384 | return register_user_hw_breakpoint(addr, len, type, triggered, | 326 | return perf_event_create_kernel_counter(attr, -1, tsk->pid, triggered); |
385 | tsk, active); | ||
386 | } | 327 | } |
387 | EXPORT_SYMBOL_GPL(modify_user_hw_breakpoint); | 328 | EXPORT_SYMBOL_GPL(modify_user_hw_breakpoint); |
388 | 329 | ||
@@ -406,8 +347,16 @@ register_kernel_hw_breakpoint_cpu(unsigned long addr, | |||
406 | int cpu, | 347 | int cpu, |
407 | bool active) | 348 | bool active) |
408 | { | 349 | { |
409 | return register_user_hw_breakpoint_cpu(addr, len, type, triggered, | 350 | DEFINE_BREAKPOINT_ATTR(attr); |
410 | -1, cpu, active); | 351 | |
352 | attr.bp_addr = addr; | ||
353 | attr.bp_len = len; | ||
354 | attr.bp_type = type; | ||
355 | |||
356 | if (!active) | ||
357 | attr.disabled = 1; | ||
358 | |||
359 | return perf_event_create_kernel_counter(&attr, cpu, -1, triggered); | ||
411 | } | 360 | } |
412 | 361 | ||
413 | /** | 362 | /** |