Fix deferred retprobes deletion
authorVasiliy Ulyanov <v.ulyanov@samsung.com>
Wed, 13 Feb 2013 12:55:15 +0000 (16:55 +0400)
committerVasiliy Ulyanov <v.ulyanov@samsung.com>
Wed, 13 Feb 2013 12:55:15 +0000 (16:55 +0400)
Every retprobe instance now saves current sp value which is used to
recover original function's return address.  Additionally this modification
may fix the issue with double return events for __switch_to function and also
possible memory leaks with __switch_to/do_exit retprobes instances.

kprobe/arch/asm-arm/dbi_kprobes.c
kprobe/arch/asm-x86/dbi_kprobes.c
kprobe/arch/dbi_kprobes.c
kprobe/dbi_kprobes.c
kprobe/dbi_kprobes.h
kprobe/dbi_kprobes_deps.c
kprobe/dbi_uprobes.c

index 92c524d..70e3e86 100644 (file)
@@ -1518,6 +1518,7 @@ void  __arch_prepare_kretprobe (struct kretprobe *rp, struct pt_regs *regs)
                ri->rp2 = NULL;
                ri->task = current;
                ri->ret_addr = (kprobe_opcode_t *) regs->uregs[14];
+               ri->sp = (kprobe_opcode_t *)regs->ARM_sp; //uregs[13];
 
                if (rp->kp.tgid)
                        if (!thumb_mode( regs ))
index 5771243..e54dd23 100644 (file)
@@ -1387,6 +1387,7 @@ void __arch_prepare_kretprobe (struct kretprobe *rp, struct pt_regs *regs)
                ri->rp = rp;
                ri->rp2 = NULL;
                ri->task = current;
+               ri->sp = (kprobe_opcode_t *)regs->EREG(sp);
 
                /* Replace the return addr with trampoline addr */
                if (rp->kp.tgid){
index 8c3ec7f..2187a04 100644 (file)
@@ -61,6 +61,7 @@
 
 extern unsigned long sched_addr;
 extern unsigned long fork_addr;
+extern unsigned long exit_addr;
 
 extern struct hlist_head kprobe_insn_pages;
 extern struct hlist_head uprobe_insn_pages;
@@ -109,6 +110,7 @@ int arch_init_module_dependencies()
 {
        sched_addr = swap_ksyms("__switch_to");
        fork_addr = swap_ksyms("do_fork");
+       exit_addr = swap_ksyms("do_exit");
 
        init_module_dependencies();
 
index 9d52be2..6280466 100644 (file)
@@ -72,6 +72,7 @@
 
 extern unsigned long sched_addr;
 extern unsigned long fork_addr;
+extern unsigned long exit_addr;
 extern struct hlist_head kprobe_insn_pages;
 
 DEFINE_PER_CPU (struct kprobe *, current_kprobe) = NULL;
@@ -684,6 +685,10 @@ int dbi_register_kretprobe (struct kretprobe *rp)
        /* Pre-allocate memory for max kretprobe instances */
        if ((unsigned long)rp->kp.addr == sched_addr) {
                rp->maxactive = SCHED_RP_NR;//max (100, 2 * NR_CPUS);
+               rp->kp.pre_handler = NULL; //not needed for __switch_to
+       } else if ((unsigned long)rp->kp.addr == exit_addr) {
+               rp->kp.pre_handler = NULL; //not needed for do_exit
+               rp->maxactive = 0;
        } else if (rp->maxactive <= 0) {
 #if 1//def CONFIG_PREEMPT
                rp->maxactive = max (COMMON_RP_NR, 2 * NR_CPUS);
@@ -720,6 +725,7 @@ int dbi_register_kretprobe (struct kretprobe *rp)
 }
 
 static void unpatch_suspended_all_task_ret_addr(struct kretprobe *rp);
+static int dbi_disarm_krp_inst(struct kretprobe_instance *ri);
 
 void dbi_unregister_kretprobe (struct kretprobe *rp)
 {
@@ -735,10 +741,12 @@ void dbi_unregister_kretprobe (struct kretprobe *rp)
 
        /* No race here */
        spin_lock_irqsave (&kretprobe_lock, flags);
-       while ((ri = get_used_rp_inst (rp)) != NULL)
-       {
-               ri->rp = NULL;
-               hlist_del (&ri->uflist);
+       while ((ri = get_used_rp_inst (rp)) != NULL) {
+               if (dbi_disarm_krp_inst(ri) == 0)
+                       recycle_rp_inst(ri);
+               else
+                       panic("%s (%d/%d): cannot disarm krp instance (%08lx)",
+                                       ri->task->comm, ri->task->tgid, ri->task->pid, rp->kp.addr);
        }
        spin_unlock_irqrestore (&kretprobe_lock, flags);
        free_rp_inst (rp);
@@ -776,6 +784,42 @@ struct kretprobe * clone_kretprobe (struct kretprobe *rp)
        return clone;
 }
 
+static int dbi_disarm_krp_inst(struct kretprobe_instance *ri)
+{
+       kprobe_opcode_t *tramp = (kprobe_opcode_t *)&kretprobe_trampoline;
+       kprobe_opcode_t *sp = ri->sp;
+       kprobe_opcode_t *found = NULL;
+       int retval = -ENOENT;
+
+       if (!sp) {
+               printk("---> %s (%d/%d) sp == NULL (%08lx)!!!!\n",
+                               ri->task->comm, ri->task->tgid, ri->task->pid,
+                               ri->rp ? ri->rp->kp.addr: NULL);
+               return -EINVAL;
+       }
+
+       while (sp > ri->sp - RETPROBE_STACK_DEPTH) {
+               if (*sp == tramp) {
+                       found = sp;
+                       break;
+               }
+               sp--;
+       }
+
+       if (found) {
+               printk("---> %s (%d/%d): trampoline found at %08lx (%08lx /%+d) - %p\n",
+                               ri->task->comm, ri->task->tgid, ri->task->pid,
+                               found, ri->sp, found - ri->sp, ri->rp ? ri->rp->kp.addr: NULL);
+               *found = ri->ret_addr;
+               retval = 0;
+       } else {
+               printk("---> %s (%d/%d): trampoline NOT found at sp = %08lx - %p\n",
+                               ri->task->comm, ri->task->tgid, ri->task->pid,
+                               ri->sp, ri->rp ? ri->rp->kp.addr: NULL);
+       }
+
+       return retval;
+}
 
 static void inline set_task_trampoline(struct task_struct *p, struct kretprobe_instance *ri, unsigned long tramp_addr)
 {
@@ -818,6 +862,7 @@ static void add_ri_pc_mod(struct task_struct *p, struct kretprobe *rp, unsigned
                ri->rp = rp;
                ri->rp2 = NULL;
                ri->task = p;
+               ri->sp = NULL;
                // set PC
                set_task_trampoline(p, ri, tramp_addr);
                add_rp_inst(ri);
index 6f79b22..8b36261 100644 (file)
@@ -74,6 +74,8 @@
 
 #define JPROBE_ENTRY(pentry)    (kprobe_opcode_t *)pentry
 
+#define RETPROBE_STACK_DEPTH 64
+
 struct kprobe;
 struct pt_regs;
 struct kretprobe;
@@ -215,6 +217,7 @@ struct kretprobe_instance
        struct hlist_node hlist;
        struct kretprobe *rp;
        kprobe_opcode_t *ret_addr;
+       kprobe_opcode_t *sp;
        struct kretprobe *rp2;
        struct task_struct *task;
 };
index bbac128..986f3ba 100644 (file)
@@ -38,6 +38,7 @@
 
 unsigned long sched_addr;
 unsigned long fork_addr;
+unsigned long exit_addr;
 
 #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29)
 struct mm_struct* init_mm_ptr;
index 1843ada..d0d80d9 100644 (file)
@@ -278,6 +278,54 @@ out:
        return ret;
 }
 
+static int dbi_disarm_urp_inst(struct kretprobe_instance *ri)
+{
+       struct task_struct *task = ri->task;
+       kprobe_opcode_t *tramp = (kprobe_opcode_t *)(ri->rp->kp.ainsn.insn +
+                       UPROBES_TRAMP_RET_BREAK_IDX);
+       kprobe_opcode_t *stack = ri->sp - RETPROBE_STACK_DEPTH;
+       kprobe_opcode_t *found = NULL;
+       kprobe_opcode_t *buf[RETPROBE_STACK_DEPTH];
+       int i, retval;
+
+       retval = read_proc_vm_atomic(task, stack, buf, sizeof(buf));
+       if (retval != sizeof(buf)) {
+               printk("---> %s (%d/%d): failed to read stack from %08lx",
+                       task->comm, task->tgid, task->pid, stack);
+               retval = -EFAULT;
+               goto out;
+       }
+
+       /* search the stack from the bottom */
+       for (i = RETPROBE_STACK_DEPTH - 1; i >= 0; i--) {
+               if (buf[i] = tramp) {
+                       found = stack + i;
+                       break;
+               }
+       }
+
+       if (found) {
+               printk("---> %s (%d/%d): trampoline found at %08lx (%08lx /%+d) - %p\n",
+                               task->comm, task->tgid, task->pid,
+                               found, ri->sp, found - ri->sp, ri->rp->kp.addr);
+               retval = write_proc_vm_atomic(task, found, &ri->ret_addr,
+                               sizeof(ri->ret_addr));
+               if (retval != sizeof(ri->ret_addr)) {
+                       printk("---> %s (%d/%d): failed to write value to %08lx",
+                               task->comm, task->tgid, task->pid, found);
+                       retval = -EFAULT;
+               } else {
+                       retval = 0;
+               }
+       } else {
+               printk("---> %s (%d/%d): trampoline NOT found at sp = %08lx - %p\n",
+                               task->comm, task->tgid, task->pid, ri->sp, ri->rp->kp.addr);
+               retval = -ENOENT;
+       }
+
+out:
+       return retval;
+}
 
 void dbi_unregister_uretprobe(struct task_struct *task, struct kretprobe *rp, int atomic, int not_rp2)
 {
@@ -287,6 +335,14 @@ void dbi_unregister_uretprobe(struct task_struct *task, struct kretprobe *rp, in
 
        spin_lock_irqsave (&kretprobe_lock, flags);
 
+       while ((ri = get_used_rp_inst(rp)) != NULL) {
+               if (dbi_disarm_urp_inst(ri) == 0)
+                       recycle_rp_inst(ri);
+               else
+                       panic("%s (%d/%d): cannot disarm urp instance (%08lx)",
+                                       ri->task->comm, ri->task->tgid, ri->task->pid, rp->kp.addr);
+       }
+
        if (hlist_empty(&rp->used_instances) || not_rp2) {
                struct kprobe *p = &rp->kp;
                // if there are no used retprobe instances (i.e. function is not entered) - disarm retprobe