parisc: Implement kretprobes
authorSven Schnelle <svens@stackframe.org>
Tue, 9 Apr 2019 17:30:28 +0000 (19:30 +0200)
committerHelge Deller <deller@gmx.de>
Fri, 3 May 2019 21:47:39 +0000 (23:47 +0200)
Implement kretprobes on parisc, parts stolen from powerpc.

Signed-off-by: Sven Schnelle <svens@stackframe.org>
Signed-off-by: Helge Deller <deller@gmx.de>
arch/parisc/Kconfig
arch/parisc/kernel/kprobes.c

index 7712688..c803816 100644 (file)
@@ -56,6 +56,7 @@ config PARISC
        select NEED_SG_DMA_LENGTH
        select HAVE_ARCH_KGDB
        select HAVE_KPROBES
+       select HAVE_KRETPROBES
 
        help
          The PA-RISC microprocessor is designed by Hewlett-Packard and used
index 8b1977c..d58960b 100644 (file)
@@ -172,6 +172,112 @@ int __kprobes parisc_kprobe_ss_handler(struct pt_regs *regs)
        return 1;
 }
 
+static inline void kretprobe_trampoline(void)
+{
+       asm volatile("nop");
+       asm volatile("nop");
+}
+
+static int __kprobes trampoline_probe_handler(struct kprobe *p,
+                                             struct pt_regs *regs);
+
+static struct kprobe trampoline_p = {
+       .pre_handler = trampoline_probe_handler
+};
+
+static int __kprobes trampoline_probe_handler(struct kprobe *p,
+                                             struct pt_regs *regs)
+{
+       struct kretprobe_instance *ri = NULL;
+       struct hlist_head *head, empty_rp;
+       struct hlist_node *tmp;
+       unsigned long flags, orig_ret_address = 0;
+       unsigned long trampoline_address = (unsigned long)trampoline_p.addr;
+       kprobe_opcode_t *correct_ret_addr = NULL;
+
+       INIT_HLIST_HEAD(&empty_rp);
+       kretprobe_hash_lock(current, &head, &flags);
+
+       /*
+        * It is possible to have multiple instances associated with a given
+        * task either because multiple functions in the call path have
+        * a return probe installed on them, and/or more than one return
+        * probe was registered for a target function.
+        *
+        * We can handle this because:
+        *     - instances are always inserted at the head of the list
+        *     - when multiple return probes are registered for the same
+        *       function, the first instance's ret_addr will point to the
+        *       real return address, and all the rest will point to
+        *       kretprobe_trampoline
+        */
+       hlist_for_each_entry_safe(ri, tmp, head, hlist) {
+               if (ri->task != current)
+                       /* another task is sharing our hash bucket */
+                       continue;
+
+               orig_ret_address = (unsigned long)ri->ret_addr;
+
+               if (orig_ret_address != trampoline_address)
+                       /*
+                        * This is the real return address. Any other
+                        * instances associated with this task are for
+                        * other calls deeper on the call stack
+                        */
+                       break;
+       }
+
+       kretprobe_assert(ri, orig_ret_address, trampoline_address);
+
+       correct_ret_addr = ri->ret_addr;
+       hlist_for_each_entry_safe(ri, tmp, head, hlist) {
+               if (ri->task != current)
+                       /* another task is sharing our hash bucket */
+                       continue;
+
+               orig_ret_address = (unsigned long)ri->ret_addr;
+               if (ri->rp && ri->rp->handler) {
+                       __this_cpu_write(current_kprobe, &ri->rp->kp);
+                       get_kprobe_ctlblk()->kprobe_status = KPROBE_HIT_ACTIVE;
+                       ri->ret_addr = correct_ret_addr;
+                       ri->rp->handler(ri, regs);
+                       __this_cpu_write(current_kprobe, NULL);
+               }
+
+               recycle_rp_inst(ri, &empty_rp);
+
+               if (orig_ret_address != trampoline_address)
+                       /*
+                        * This is the real return address. Any other
+                        * instances associated with this task are for
+                        * other calls deeper on the call stack
+                        */
+                       break;
+       }
+
+       kretprobe_hash_unlock(current, &flags);
+
+       hlist_for_each_entry_safe(ri, tmp, &empty_rp, hlist) {
+               hlist_del(&ri->hlist);
+               kfree(ri);
+       }
+       instruction_pointer_set(regs, orig_ret_address);
+       return 1;
+}
+
+void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
+                                     struct pt_regs *regs)
+{
+       ri->ret_addr = (kprobe_opcode_t *)regs->gr[2];
+
+       /* Replace the return addr with trampoline addr. */
+       regs->gr[2] = (unsigned long)trampoline_p.addr;
+}
+
+int __kprobes arch_trampoline_kprobe(struct kprobe *p)
+{
+       return p->addr == trampoline_p.addr;
+}
 bool arch_kprobe_on_func_entry(unsigned long offset)
 {
        return !offset;
@@ -179,5 +285,7 @@ bool arch_kprobe_on_func_entry(unsigned long offset)
 
 int __init arch_init_kprobes(void)
 {
-       return 0;
+       trampoline_p.addr = (kprobe_opcode_t *)
+               dereference_function_descriptor(kretprobe_trampoline);
+       return register_kprobe(&trampoline_p);
 }