]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
kprobes: support per-kprobe disabling
authorMasami Hiramatsu <mhiramat@redhat.com>
Tue, 7 Apr 2009 02:01:02 +0000 (19:01 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 7 Apr 2009 15:31:08 +0000 (08:31 -0700)
Add disable_kprobe() and enable_kprobe() to disable/enable kprobes
temporarily.

disable_kprobe() asynchronously disables probe handlers of specified
kprobe.  So, after calling it, some handlers can be called at a while.
enable_kprobe() enables specified kprobe.

aggr_pre_handler and aggr_post_handler check disabled probes.  On the
other hand aggr_break_handler and aggr_fault_handler don't check it
because these handlers will be called while executing pre or post handlers
and usually those help error handling.

Signed-off-by: Masami Hiramatsu <mhiramat@redhat.com>
Acked-by: Ananth N Mavinakayanahalli <ananth@in.ibm.com>
Cc: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
Cc: David S. Miller <davem@davemloft.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/kprobes.txt
include/linux/kprobes.h
kernel/kprobes.c

index 48b3de90eb1eb03ceb1929e4228ae3a57debc526..f609af242d6cc07369bdc3ee187a64b298314263 100644 (file)
@@ -212,7 +212,9 @@ hit, Kprobes calls kp->pre_handler.  After the probed instruction
 is single-stepped, Kprobe calls kp->post_handler.  If a fault
 occurs during execution of kp->pre_handler or kp->post_handler,
 or during single-stepping of the probed instruction, Kprobes calls
-kp->fault_handler.  Any or all handlers can be NULL.
+kp->fault_handler.  Any or all handlers can be NULL. If kp->flags
+is set KPROBE_FLAG_DISABLED, that kp will be registered but disabled,
+so, it's handlers aren't hit until calling enable_kprobe(kp).
 
 NOTE:
 1. With the introduction of the "symbol_name" field to struct kprobe,
@@ -363,6 +365,22 @@ probes) in the specified array, they clear the addr field of those
 incorrect probes. However, other probes in the array are
 unregistered correctly.
 
+4.7 disable_kprobe
+
+#include <linux/kprobes.h>
+int disable_kprobe(struct kprobe *kp);
+
+Temporarily disables the specified kprobe. You can enable it again by using
+enable_kprobe(). You must specify the kprobe which has been registered.
+
+4.8 enable_kprobe
+
+#include <linux/kprobes.h>
+int enable_kprobe(struct kprobe *kp);
+
+Enables kprobe which has been disabled by disable_kprobe(). You must specify
+the kprobe which has been registered.
+
 5. Kprobes Features and Limitations
 
 Kprobes allows multiple probes at the same address.  Currently,
@@ -500,10 +518,14 @@ the probe. If the probed function belongs to a module, the module name
 is also specified. Following columns show probe status. If the probe is on
 a virtual address that is no longer valid (module init sections, module
 virtual addresses that correspond to modules that've been unloaded),
-such probes are marked with [GONE].
+such probes are marked with [GONE]. If the probe is temporarily disabled,
+such probes are marked with [DISABLED].
 
-/debug/kprobes/enabled: Turn kprobes ON/OFF
+/debug/kprobes/enabled: Turn kprobes ON/OFF forcibly.
 
-Provides a knob to globally turn registered kprobes ON or OFF. By default,
-all kprobes are enabled. By echoing "0" to this file, all registered probes
-will be disarmed, till such time a "1" is echoed to this file.
+Provides a knob to globally and forcibly turn registered kprobes ON or OFF.
+By default, all kprobes are enabled. By echoing "0" to this file, all
+registered probes will be disarmed, till such time a "1" is echoed to this
+file. Note that this knob just disarms and arms all kprobes and doesn't
+change each probe's disabling state. This means that disabled kprobes (marked
+[DISABLED]) will be not enabled if you turn ON all kprobes by this knob.
index 39826a6783646295330a2707e01d277583798313..1071cfddddc9877fcaa93fbbcaf5ad89dd7d243f 100644 (file)
@@ -112,18 +112,28 @@ struct kprobe {
        /* copy of the original instruction */
        struct arch_specific_insn ainsn;
 
-       /* Indicates various status flags.  Protected by kprobe_mutex. */
+       /*
+        * Indicates various status flags.
+        * Protected by kprobe_mutex after this kprobe is registered.
+        */
        u32 flags;
 };
 
 /* Kprobe status flags */
 #define KPROBE_FLAG_GONE       1 /* breakpoint has already gone */
+#define KPROBE_FLAG_DISABLED   2 /* probe is temporarily disabled */
 
+/* Has this kprobe gone ? */
 static inline int kprobe_gone(struct kprobe *p)
 {
        return p->flags & KPROBE_FLAG_GONE;
 }
 
+/* Is this kprobe disabled ? */
+static inline int kprobe_disabled(struct kprobe *p)
+{
+       return p->flags & (KPROBE_FLAG_DISABLED | KPROBE_FLAG_GONE);
+}
 /*
  * Special probe type that uses setjmp-longjmp type tricks to resume
  * execution at a specified entry with a matching prototype corresponding
@@ -283,6 +293,9 @@ void unregister_kretprobes(struct kretprobe **rps, int num);
 void kprobe_flush_task(struct task_struct *tk);
 void recycle_rp_inst(struct kretprobe_instance *ri, struct hlist_head *head);
 
+int disable_kprobe(struct kprobe *kp);
+int enable_kprobe(struct kprobe *kp);
+
 #else /* !CONFIG_KPROBES: */
 
 static inline int kprobes_built_in(void)
@@ -349,5 +362,13 @@ static inline void unregister_kretprobes(struct kretprobe **rps, int num)
 static inline void kprobe_flush_task(struct task_struct *tk)
 {
 }
+static inline int disable_kprobe(struct kprobe *kp)
+{
+       return -ENOSYS;
+}
+static inline int enable_kprobe(struct kprobe *kp)
+{
+       return -ENOSYS;
+}
 #endif /* CONFIG_KPROBES */
 #endif /* _LINUX_KPROBES_H */
index dae198b68e9705bea8f4e198fec9ea2c2f236a56..a5e74ddee0e2f3a9937e46e95eae61e9b44b8746 100644 (file)
@@ -328,7 +328,7 @@ static int __kprobes aggr_pre_handler(struct kprobe *p, struct pt_regs *regs)
        struct kprobe *kp;
 
        list_for_each_entry_rcu(kp, &p->list, list) {
-               if (kp->pre_handler && !kprobe_gone(kp)) {
+               if (kp->pre_handler && likely(!kprobe_disabled(kp))) {
                        set_kprobe_instance(kp);
                        if (kp->pre_handler(kp, regs))
                                return 1;
@@ -344,7 +344,7 @@ static void __kprobes aggr_post_handler(struct kprobe *p, struct pt_regs *regs,
        struct kprobe *kp;
 
        list_for_each_entry_rcu(kp, &p->list, list) {
-               if (kp->post_handler && !kprobe_gone(kp)) {
+               if (kp->post_handler && likely(!kprobe_disabled(kp))) {
                        set_kprobe_instance(kp);
                        kp->post_handler(kp, regs, flags);
                        reset_kprobe_instance();
@@ -523,6 +523,7 @@ static inline void copy_kprobe(struct kprobe *old_p, struct kprobe *p)
 */
 static int __kprobes add_new_kprobe(struct kprobe *ap, struct kprobe *p)
 {
+       BUG_ON(kprobe_gone(ap) || kprobe_gone(p));
        if (p->break_handler) {
                if (ap->break_handler)
                        return -EEXIST;
@@ -532,6 +533,13 @@ static int __kprobes add_new_kprobe(struct kprobe *ap, struct kprobe *p)
                list_add_rcu(&p->list, &ap->list);
        if (p->post_handler && !ap->post_handler)
                ap->post_handler = aggr_post_handler;
+
+       if (kprobe_disabled(ap) && !kprobe_disabled(p)) {
+               ap->flags &= ~KPROBE_FLAG_DISABLED;
+               if (!kprobes_all_disarmed)
+                       /* Arm the breakpoint again. */
+                       arch_arm_kprobe(ap);
+       }
        return 0;
 }
 
@@ -592,20 +600,36 @@ static int __kprobes register_aggr_kprobe(struct kprobe *old_p,
                         * freed by unregister_kprobe.
                         */
                        return ret;
-               /* Clear gone flag to prevent allocating new slot again. */
-               ap->flags &= ~KPROBE_FLAG_GONE;
+
                /*
-                * If the old_p has gone, its breakpoint has been disarmed.
-                * We have to arm it again after preparing real kprobes.
+                * Clear gone flag to prevent allocating new slot again, and
+                * set disabled flag because it is not armed yet.
                 */
-               if (!kprobes_all_disarmed)
-                       arch_arm_kprobe(ap);
+               ap->flags = (ap->flags & ~KPROBE_FLAG_GONE)
+                           | KPROBE_FLAG_DISABLED;
        }
 
        copy_kprobe(ap, p);
        return add_new_kprobe(ap, p);
 }
 
+/* Try to disable aggr_kprobe, and return 1 if succeeded.*/
+static int __kprobes try_to_disable_aggr_kprobe(struct kprobe *p)
+{
+       struct kprobe *kp;
+
+       list_for_each_entry_rcu(kp, &p->list, list) {
+               if (!kprobe_disabled(kp))
+                       /*
+                        * There is an active probe on the list.
+                        * We can't disable aggr_kprobe.
+                        */
+                       return 0;
+       }
+       p->flags |= KPROBE_FLAG_DISABLED;
+       return 1;
+}
+
 static int __kprobes in_kprobes_functions(unsigned long addr)
 {
        struct kprobe_blackpoint *kb;
@@ -664,7 +688,9 @@ int __kprobes register_kprobe(struct kprobe *p)
                return -EINVAL;
        }
 
-       p->flags = 0;
+       /* User can pass only KPROBE_FLAG_DISABLED to register_kprobe */
+       p->flags &= KPROBE_FLAG_DISABLED;
+
        /*
         * Check if are we probing a module.
         */
@@ -709,7 +735,7 @@ int __kprobes register_kprobe(struct kprobe *p)
        hlist_add_head_rcu(&p->hlist,
                       &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
 
-       if (!kprobes_all_disarmed)
+       if (!kprobes_all_disarmed && !kprobe_disabled(p))
                arch_arm_kprobe(p);
 
 out_unlock_text:
@@ -724,25 +750,37 @@ out:
 }
 EXPORT_SYMBOL_GPL(register_kprobe);
 
-/*
- * Unregister a kprobe without a scheduler synchronization.
- */
-static int __kprobes __unregister_kprobe_top(struct kprobe *p)
+/* Check passed kprobe is valid and return kprobe in kprobe_table. */
+static struct kprobe * __kprobes __get_valid_kprobe(struct kprobe *p)
 {
        struct kprobe *old_p, *list_p;
 
        old_p = get_kprobe(p->addr);
        if (unlikely(!old_p))
-               return -EINVAL;
+               return NULL;
 
        if (p != old_p) {
                list_for_each_entry_rcu(list_p, &old_p->list, list)
                        if (list_p == p)
                        /* kprobe p is a valid probe */
-                               goto valid_p;
-               return -EINVAL;
+                               goto valid;
+               return NULL;
        }
-valid_p:
+valid:
+       return old_p;
+}
+
+/*
+ * Unregister a kprobe without a scheduler synchronization.
+ */
+static int __kprobes __unregister_kprobe_top(struct kprobe *p)
+{
+       struct kprobe *old_p, *list_p;
+
+       old_p = __get_valid_kprobe(p);
+       if (old_p == NULL)
+               return -EINVAL;
+
        if (old_p == p ||
            (old_p->pre_handler == aggr_pre_handler &&
             list_is_singular(&old_p->list))) {
@@ -751,7 +789,7 @@ valid_p:
                 * enabled and not gone - otherwise, the breakpoint would
                 * already have been removed. We save on flushing icache.
                 */
-               if (!kprobes_all_disarmed && !kprobe_gone(old_p)) {
+               if (!kprobes_all_disarmed && !kprobe_disabled(old_p)) {
                        mutex_lock(&text_mutex);
                        arch_disarm_kprobe(p);
                        mutex_unlock(&text_mutex);
@@ -769,6 +807,11 @@ valid_p:
                }
 noclean:
                list_del_rcu(&p->list);
+               if (!kprobe_disabled(old_p)) {
+                       try_to_disable_aggr_kprobe(old_p);
+                       if (!kprobes_all_disarmed && kprobe_disabled(old_p))
+                               arch_disarm_kprobe(old_p);
+               }
        }
        return 0;
 }
@@ -1078,6 +1121,7 @@ static int __kprobes pre_handler_kretprobe(struct kprobe *p,
 static void __kprobes kill_kprobe(struct kprobe *p)
 {
        struct kprobe *kp;
+
        p->flags |= KPROBE_FLAG_GONE;
        if (p->pre_handler == aggr_pre_handler) {
                /*
@@ -1219,12 +1263,18 @@ static void __kprobes report_probe(struct seq_file *pi, struct kprobe *p,
        else
                kprobe_type = "k";
        if (sym)
-               seq_printf(pi, "%p  %s  %s+0x%x  %s %s\n", p->addr, kprobe_type,
-                       sym, offset, (modname ? modname : " "),
-                       (kprobe_gone(p) ? "[GONE]" : ""));
+               seq_printf(pi, "%p  %s  %s+0x%x  %s %s%s\n",
+                       p->addr, kprobe_type, sym, offset,
+                       (modname ? modname : " "),
+                       (kprobe_gone(p) ? "[GONE]" : ""),
+                       ((kprobe_disabled(p) && !kprobe_gone(p)) ?
+                        "[DISABLED]" : ""));
        else
-               seq_printf(pi, "%p  %s  %p %s\n", p->addr, kprobe_type, p->addr,
-                       (kprobe_gone(p) ? "[GONE]" : ""));
+               seq_printf(pi, "%p  %s  %p %s%s\n",
+                       p->addr, kprobe_type, p->addr,
+                       (kprobe_gone(p) ? "[GONE]" : ""),
+                       ((kprobe_disabled(p) && !kprobe_gone(p)) ?
+                        "[DISABLED]" : ""));
 }
 
 static void __kprobes *kprobe_seq_start(struct seq_file *f, loff_t *pos)
@@ -1289,6 +1339,71 @@ static struct file_operations debugfs_kprobes_operations = {
        .release        = seq_release,
 };
 
+/* Disable one kprobe */
+int __kprobes disable_kprobe(struct kprobe *kp)
+{
+       int ret = 0;
+       struct kprobe *p;
+
+       mutex_lock(&kprobe_mutex);
+
+       /* Check whether specified probe is valid. */
+       p = __get_valid_kprobe(kp);
+       if (unlikely(p == NULL)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* If the probe is already disabled (or gone), just return */
+       if (kprobe_disabled(kp))
+               goto out;
+
+       kp->flags |= KPROBE_FLAG_DISABLED;
+       if (p != kp)
+               /* When kp != p, p is always enabled. */
+               try_to_disable_aggr_kprobe(p);
+
+       if (!kprobes_all_disarmed && kprobe_disabled(p))
+               arch_disarm_kprobe(p);
+out:
+       mutex_unlock(&kprobe_mutex);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(disable_kprobe);
+
+/* Enable one kprobe */
+int __kprobes enable_kprobe(struct kprobe *kp)
+{
+       int ret = 0;
+       struct kprobe *p;
+
+       mutex_lock(&kprobe_mutex);
+
+       /* Check whether specified probe is valid. */
+       p = __get_valid_kprobe(kp);
+       if (unlikely(p == NULL)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (kprobe_gone(kp)) {
+               /* This kprobe has gone, we couldn't enable it. */
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (!kprobes_all_disarmed && kprobe_disabled(p))
+               arch_arm_kprobe(p);
+
+       p->flags &= ~KPROBE_FLAG_DISABLED;
+       if (p != kp)
+               kp->flags &= ~KPROBE_FLAG_DISABLED;
+out:
+       mutex_unlock(&kprobe_mutex);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(enable_kprobe);
+
 static void __kprobes arm_all_kprobes(void)
 {
        struct hlist_head *head;
@@ -1306,7 +1421,7 @@ static void __kprobes arm_all_kprobes(void)
        for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
                head = &kprobe_table[i];
                hlist_for_each_entry_rcu(p, node, head, hlist)
-                       if (!kprobe_gone(p))
+                       if (!kprobe_disabled(p))
                                arch_arm_kprobe(p);
        }
        mutex_unlock(&text_mutex);
@@ -1338,7 +1453,7 @@ static void __kprobes disarm_all_kprobes(void)
        for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
                head = &kprobe_table[i];
                hlist_for_each_entry_rcu(p, node, head, hlist) {
-                       if (!arch_trampoline_kprobe(p) && !kprobe_gone(p))
+                       if (!arch_trampoline_kprobe(p) && !kprobe_disabled(p))
                                arch_disarm_kprobe(p);
                }
        }