|
@@ -43,9 +43,11 @@
|
|
|
#include <linux/seq_file.h>
|
|
|
#include <linux/debugfs.h>
|
|
|
#include <linux/kdebug.h>
|
|
|
+
|
|
|
#include <asm-generic/sections.h>
|
|
|
#include <asm/cacheflush.h>
|
|
|
#include <asm/errno.h>
|
|
|
+#include <asm/uaccess.h>
|
|
|
|
|
|
#define KPROBE_HASH_BITS 6
|
|
|
#define KPROBE_TABLE_SIZE (1 << KPROBE_HASH_BITS)
|
|
@@ -64,6 +66,9 @@ static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE];
|
|
|
static struct hlist_head kretprobe_inst_table[KPROBE_TABLE_SIZE];
|
|
|
static atomic_t kprobe_count;
|
|
|
|
|
|
+/* NOTE: change this value only with kprobe_mutex held */
|
|
|
+static bool kprobe_enabled;
|
|
|
+
|
|
|
DEFINE_MUTEX(kprobe_mutex); /* Protects kprobe_table */
|
|
|
DEFINE_SPINLOCK(kretprobe_lock); /* Protects kretprobe_inst_table */
|
|
|
static DEFINE_PER_CPU(struct kprobe *, kprobe_instance) = NULL;
|
|
@@ -564,12 +569,13 @@ static int __kprobes __register_kprobe(struct kprobe *p,
|
|
|
hlist_add_head_rcu(&p->hlist,
|
|
|
&kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
|
|
|
|
|
|
- if (atomic_add_return(1, &kprobe_count) == \
|
|
|
+ if (kprobe_enabled) {
|
|
|
+ if (atomic_add_return(1, &kprobe_count) == \
|
|
|
(ARCH_INACTIVE_KPROBE_COUNT + 1))
|
|
|
- register_page_fault_notifier(&kprobe_page_fault_nb);
|
|
|
-
|
|
|
- arch_arm_kprobe(p);
|
|
|
+ register_page_fault_notifier(&kprobe_page_fault_nb);
|
|
|
|
|
|
+ arch_arm_kprobe(p);
|
|
|
+ }
|
|
|
out:
|
|
|
mutex_unlock(&kprobe_mutex);
|
|
|
|
|
@@ -607,8 +613,13 @@ valid_p:
|
|
|
if (old_p == p ||
|
|
|
(old_p->pre_handler == aggr_pre_handler &&
|
|
|
p->list.next == &old_p->list && p->list.prev == &old_p->list)) {
|
|
|
- /* Only probe on the hash list */
|
|
|
- arch_disarm_kprobe(p);
|
|
|
+ /*
|
|
|
+ * Only probe on the hash list. Disarm only if kprobes are
|
|
|
+ * enabled - otherwise, the breakpoint would already have
|
|
|
+ * been removed. We save on flushing icache.
|
|
|
+ */
|
|
|
+ if (kprobe_enabled)
|
|
|
+ arch_disarm_kprobe(p);
|
|
|
hlist_del_rcu(&old_p->hlist);
|
|
|
cleanup_p = 1;
|
|
|
} else {
|
|
@@ -797,6 +808,9 @@ static int __init init_kprobes(void)
|
|
|
}
|
|
|
atomic_set(&kprobe_count, 0);
|
|
|
|
|
|
+ /* By default, kprobes are enabled */
|
|
|
+ kprobe_enabled = true;
|
|
|
+
|
|
|
err = arch_init_kprobes();
|
|
|
if (!err)
|
|
|
err = register_die_notifier(&kprobe_exceptions_nb);
|
|
@@ -806,7 +820,7 @@ static int __init init_kprobes(void)
|
|
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
static void __kprobes report_probe(struct seq_file *pi, struct kprobe *p,
|
|
|
- const char *sym, int offset,char *modname)
|
|
|
+ const char *sym, int offset,char *modname)
|
|
|
{
|
|
|
char *kprobe_type;
|
|
|
|
|
@@ -885,9 +899,130 @@ static struct file_operations debugfs_kprobes_operations = {
|
|
|
.release = seq_release,
|
|
|
};
|
|
|
|
|
|
+static void __kprobes enable_all_kprobes(void)
|
|
|
+{
|
|
|
+ struct hlist_head *head;
|
|
|
+ struct hlist_node *node;
|
|
|
+ struct kprobe *p;
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ mutex_lock(&kprobe_mutex);
|
|
|
+
|
|
|
+ /* If kprobes are already enabled, just return */
|
|
|
+ if (kprobe_enabled)
|
|
|
+ goto already_enabled;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Re-register the page fault notifier only if there are any
|
|
|
+ * active probes at the time of enabling kprobes globally
|
|
|
+ */
|
|
|
+ if (atomic_read(&kprobe_count) > ARCH_INACTIVE_KPROBE_COUNT)
|
|
|
+ register_page_fault_notifier(&kprobe_page_fault_nb);
|
|
|
+
|
|
|
+ for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
|
|
|
+ head = &kprobe_table[i];
|
|
|
+ hlist_for_each_entry_rcu(p, node, head, hlist)
|
|
|
+ arch_arm_kprobe(p);
|
|
|
+ }
|
|
|
+
|
|
|
+ kprobe_enabled = true;
|
|
|
+ printk(KERN_INFO "Kprobes globally enabled\n");
|
|
|
+
|
|
|
+already_enabled:
|
|
|
+ mutex_unlock(&kprobe_mutex);
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static void __kprobes disable_all_kprobes(void)
|
|
|
+{
|
|
|
+ struct hlist_head *head;
|
|
|
+ struct hlist_node *node;
|
|
|
+ struct kprobe *p;
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ mutex_lock(&kprobe_mutex);
|
|
|
+
|
|
|
+ /* If kprobes are already disabled, just return */
|
|
|
+ if (!kprobe_enabled)
|
|
|
+ goto already_disabled;
|
|
|
+
|
|
|
+ kprobe_enabled = false;
|
|
|
+ printk(KERN_INFO "Kprobes globally disabled\n");
|
|
|
+ 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))
|
|
|
+ arch_disarm_kprobe(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&kprobe_mutex);
|
|
|
+ /* Allow all currently running kprobes to complete */
|
|
|
+ synchronize_sched();
|
|
|
+
|
|
|
+ mutex_lock(&kprobe_mutex);
|
|
|
+ /* Unconditionally unregister the page_fault notifier */
|
|
|
+ unregister_page_fault_notifier(&kprobe_page_fault_nb);
|
|
|
+
|
|
|
+already_disabled:
|
|
|
+ mutex_unlock(&kprobe_mutex);
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * XXX: The debugfs bool file interface doesn't allow for callbacks
|
|
|
+ * when the bool state is switched. We can reuse that facility when
|
|
|
+ * available
|
|
|
+ */
|
|
|
+static ssize_t read_enabled_file_bool(struct file *file,
|
|
|
+ char __user *user_buf, size_t count, loff_t *ppos)
|
|
|
+{
|
|
|
+ char buf[3];
|
|
|
+
|
|
|
+ if (kprobe_enabled)
|
|
|
+ buf[0] = '1';
|
|
|
+ else
|
|
|
+ buf[0] = '0';
|
|
|
+ buf[1] = '\n';
|
|
|
+ buf[2] = 0x00;
|
|
|
+ return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t write_enabled_file_bool(struct file *file,
|
|
|
+ const char __user *user_buf, size_t count, loff_t *ppos)
|
|
|
+{
|
|
|
+ char buf[32];
|
|
|
+ int buf_size;
|
|
|
+
|
|
|
+ buf_size = min(count, (sizeof(buf)-1));
|
|
|
+ if (copy_from_user(buf, user_buf, buf_size))
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ switch (buf[0]) {
|
|
|
+ case 'y':
|
|
|
+ case 'Y':
|
|
|
+ case '1':
|
|
|
+ enable_all_kprobes();
|
|
|
+ break;
|
|
|
+ case 'n':
|
|
|
+ case 'N':
|
|
|
+ case '0':
|
|
|
+ disable_all_kprobes();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+static struct file_operations fops_kp = {
|
|
|
+ .read = read_enabled_file_bool,
|
|
|
+ .write = write_enabled_file_bool,
|
|
|
+};
|
|
|
+
|
|
|
static int __kprobes debugfs_kprobe_init(void)
|
|
|
{
|
|
|
struct dentry *dir, *file;
|
|
|
+ unsigned int value = 1;
|
|
|
|
|
|
dir = debugfs_create_dir("kprobes", NULL);
|
|
|
if (!dir)
|
|
@@ -900,6 +1035,13 @@ static int __kprobes debugfs_kprobe_init(void)
|
|
|
return -ENOMEM;
|
|
|
}
|
|
|
|
|
|
+ file = debugfs_create_file("enabled", 0600, dir,
|
|
|
+ &value, &fops_kp);
|
|
|
+ if (!file) {
|
|
|
+ debugfs_remove(dir);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|