uretprobes: Return probe exit, invoke handlers
Anton Arapov [Wed, 3 Apr 2013 16:00:36 +0000 (18:00 +0200)]
Uretprobe handlers are invoked when the trampoline is hit, on completion
the trampoline is replaced with the saved return address and the uretprobe
instance deleted.

TODO: handle_trampoline() assumes that ->return_instances is always valid.
We should teach it to handle longjmp() which can invalidate the pending
return_instance's. This is nontrivial, we will try to do this in a separate
series.

Signed-off-by: Anton Arapov <anton@redhat.com>
Acked-by: Srikar Dronamraju <srikar@linux.vnet.ibm.com>
Signed-off-by: Oleg Nesterov <oleg@redhat.com>

kernel/events/uprobes.c

index 3798947..65429ad 100644 (file)
@@ -1633,6 +1633,62 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
        up_read(&uprobe->register_rwsem);
 }
 
+static void
+handle_uretprobe_chain(struct return_instance *ri, struct pt_regs *regs)
+{
+       struct uprobe *uprobe = ri->uprobe;
+       struct uprobe_consumer *uc;
+
+       down_read(&uprobe->register_rwsem);
+       for (uc = uprobe->consumers; uc; uc = uc->next) {
+               if (uc->ret_handler)
+                       uc->ret_handler(uc, ri->func, regs);
+       }
+       up_read(&uprobe->register_rwsem);
+}
+
+static bool handle_trampoline(struct pt_regs *regs)
+{
+       struct uprobe_task *utask;
+       struct return_instance *ri, *tmp;
+       bool chained;
+
+       utask = current->utask;
+       if (!utask)
+               return false;
+
+       ri = utask->return_instances;
+       if (!ri)
+               return false;
+
+       /*
+        * TODO: we should throw out return_instance's invalidated by
+        * longjmp(), currently we assume that the probed function always
+        * returns.
+        */
+       instruction_pointer_set(regs, ri->orig_ret_vaddr);
+
+       for (;;) {
+               handle_uretprobe_chain(ri, regs);
+
+               chained = ri->chained;
+               put_uprobe(ri->uprobe);
+
+               tmp = ri;
+               ri = ri->next;
+               kfree(tmp);
+
+               if (!chained)
+                       break;
+
+               BUG_ON(!ri);
+       }
+
+       utask->return_instances = ri;
+
+       return true;
+}
+
 /*
  * Run handler and ask thread to singlestep.
  * Ensure all non-fatal signals cannot interrupt thread while it singlesteps.
@@ -1644,8 +1700,15 @@ static void handle_swbp(struct pt_regs *regs)
        int uninitialized_var(is_swbp);
 
        bp_vaddr = uprobe_get_swbp_addr(regs);
-       uprobe = find_active_uprobe(bp_vaddr, &is_swbp);
+       if (bp_vaddr == get_trampoline_vaddr()) {
+               if (handle_trampoline(regs))
+                       return;
 
+               pr_warn("uprobe: unable to handle uretprobe pid/tgid=%d/%d\n",
+                                               current->pid, current->tgid);
+       }
+
+       uprobe = find_active_uprobe(bp_vaddr, &is_swbp);
        if (!uprobe) {
                if (is_swbp > 0) {
                        /* No matching uprobe; signal SIGTRAP. */