diff options
author | Sandeepa Prabhu <sandeepa.s.prabhu@gmail.com> | 2016-07-08 12:35:53 -0400 |
---|---|---|
committer | Catalin Marinas <catalin.marinas@arm.com> | 2016-07-19 10:03:22 -0400 |
commit | fcfd708b8cf86b8c1ca6ce014d50287f61c0eb88 (patch) | |
tree | af73c37b2b2fbbce3af32408eed8d8a0b19072b5 | |
parent | da6a91252ad98d49b49e83b76c1f032cdf6e5258 (diff) |
arm64: Add kernel return probes support (kretprobes)
The pre-handler of this special 'trampoline' kprobe executes the return
probe handler functions and restores original return address in ELR_EL1.
This way the saved pt_regs still hold the original register context to be
carried back to the probed kernel function.
Signed-off-by: Sandeepa Prabhu <sandeepa.s.prabhu@gmail.com>
Signed-off-by: David A. Long <dave.long@linaro.org>
Acked-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
-rw-r--r-- | arch/arm64/Kconfig | 1 | ||||
-rw-r--r-- | arch/arm64/kernel/probes/kprobes.c | 90 |
2 files changed, 90 insertions, 1 deletions
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1f7d644f7f36..6af0e2e5c710 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig | |||
@@ -89,6 +89,7 @@ config ARM64 | |||
89 | select HAVE_RCU_TABLE_FREE | 89 | select HAVE_RCU_TABLE_FREE |
90 | select HAVE_SYSCALL_TRACEPOINTS | 90 | select HAVE_SYSCALL_TRACEPOINTS |
91 | select HAVE_KPROBES | 91 | select HAVE_KPROBES |
92 | select HAVE_KRETPROBES if HAVE_KPROBES | ||
92 | select IOMMU_DMA if IOMMU_SUPPORT | 93 | select IOMMU_DMA if IOMMU_SUPPORT |
93 | select IRQ_DOMAIN | 94 | select IRQ_DOMAIN |
94 | select IRQ_FORCED_THREADING | 95 | select IRQ_FORCED_THREADING |
diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c index be1f074b5736..9c70e8812ea9 100644 --- a/arch/arm64/kernel/probes/kprobes.c +++ b/arch/arm64/kernel/probes/kprobes.c | |||
@@ -578,7 +578,95 @@ bool arch_within_kprobe_blacklist(unsigned long addr) | |||
578 | 578 | ||
579 | void __kprobes __used *trampoline_probe_handler(struct pt_regs *regs) | 579 | void __kprobes __used *trampoline_probe_handler(struct pt_regs *regs) |
580 | { | 580 | { |
581 | return NULL; | 581 | struct kretprobe_instance *ri = NULL; |
582 | struct hlist_head *head, empty_rp; | ||
583 | struct hlist_node *tmp; | ||
584 | unsigned long flags, orig_ret_address = 0; | ||
585 | unsigned long trampoline_address = | ||
586 | (unsigned long)&kretprobe_trampoline; | ||
587 | kprobe_opcode_t *correct_ret_addr = NULL; | ||
588 | |||
589 | INIT_HLIST_HEAD(&empty_rp); | ||
590 | kretprobe_hash_lock(current, &head, &flags); | ||
591 | |||
592 | /* | ||
593 | * It is possible to have multiple instances associated with a given | ||
594 | * task either because multiple functions in the call path have | ||
595 | * return probes installed on them, and/or more than one | ||
596 | * return probe was registered for a target function. | ||
597 | * | ||
598 | * We can handle this because: | ||
599 | * - instances are always pushed into the head of the list | ||
600 | * - when multiple return probes are registered for the same | ||
601 | * function, the (chronologically) first instance's ret_addr | ||
602 | * will be the real return address, and all the rest will | ||
603 | * point to kretprobe_trampoline. | ||
604 | */ | ||
605 | hlist_for_each_entry_safe(ri, tmp, head, hlist) { | ||
606 | if (ri->task != current) | ||
607 | /* another task is sharing our hash bucket */ | ||
608 | continue; | ||
609 | |||
610 | orig_ret_address = (unsigned long)ri->ret_addr; | ||
611 | |||
612 | if (orig_ret_address != trampoline_address) | ||
613 | /* | ||
614 | * This is the real return address. Any other | ||
615 | * instances associated with this task are for | ||
616 | * other calls deeper on the call stack | ||
617 | */ | ||
618 | break; | ||
619 | } | ||
620 | |||
621 | kretprobe_assert(ri, orig_ret_address, trampoline_address); | ||
622 | |||
623 | correct_ret_addr = ri->ret_addr; | ||
624 | hlist_for_each_entry_safe(ri, tmp, head, hlist) { | ||
625 | if (ri->task != current) | ||
626 | /* another task is sharing our hash bucket */ | ||
627 | continue; | ||
628 | |||
629 | orig_ret_address = (unsigned long)ri->ret_addr; | ||
630 | if (ri->rp && ri->rp->handler) { | ||
631 | __this_cpu_write(current_kprobe, &ri->rp->kp); | ||
632 | get_kprobe_ctlblk()->kprobe_status = KPROBE_HIT_ACTIVE; | ||
633 | ri->ret_addr = correct_ret_addr; | ||
634 | ri->rp->handler(ri, regs); | ||
635 | __this_cpu_write(current_kprobe, NULL); | ||
636 | } | ||
637 | |||
638 | recycle_rp_inst(ri, &empty_rp); | ||
639 | |||
640 | if (orig_ret_address != trampoline_address) | ||
641 | /* | ||
642 | * This is the real return address. Any other | ||
643 | * instances associated with this task are for | ||
644 | * other calls deeper on the call stack | ||
645 | */ | ||
646 | break; | ||
647 | } | ||
648 | |||
649 | kretprobe_hash_unlock(current, &flags); | ||
650 | |||
651 | hlist_for_each_entry_safe(ri, tmp, &empty_rp, hlist) { | ||
652 | hlist_del(&ri->hlist); | ||
653 | kfree(ri); | ||
654 | } | ||
655 | return (void *)orig_ret_address; | ||
656 | } | ||
657 | |||
658 | void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri, | ||
659 | struct pt_regs *regs) | ||
660 | { | ||
661 | ri->ret_addr = (kprobe_opcode_t *)regs->regs[30]; | ||
662 | |||
663 | /* replace return addr (x30) with trampoline */ | ||
664 | regs->regs[30] = (long)&kretprobe_trampoline; | ||
665 | } | ||
666 | |||
667 | int __kprobes arch_trampoline_kprobe(struct kprobe *p) | ||
668 | { | ||
669 | return 0; | ||
582 | } | 670 | } |
583 | 671 | ||
584 | int __init arch_init_kprobes(void) | 672 | int __init arch_init_kprobes(void) |