[PATCH] kprobes: fix single-step out of line - take2
Ananth N Mavinakayanahalli [Mon, 27 Jun 2005 22:17:01 +0000 (15:17 -0700)]
Now that PPC64 has no-execute support, here is a second try to fix the
single step out of line during kprobe execution.  Kprobes on x86_64 already
solved this problem by allocating an executable page and using it as the
scratch area for stepping out of line.  Reuse that.

Signed-off-by: Ananth N Mavinakayanahalli <ananth@in.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

arch/ppc64/kernel/kprobes.c
arch/x86_64/kernel/kprobes.c
include/asm-ia64/kprobes.h
include/asm-ppc64/kprobes.h
include/linux/kprobes.h
kernel/kprobes.c

index 782ce3e..86cc549 100644 (file)
@@ -36,6 +36,8 @@
 #include <asm/kdebug.h>
 #include <asm/sstep.h>
 
+static DECLARE_MUTEX(kprobe_mutex);
+
 static struct kprobe *current_kprobe;
 static unsigned long kprobe_status, kprobe_saved_msr;
 static struct kprobe *kprobe_prev;
@@ -54,6 +56,15 @@ int arch_prepare_kprobe(struct kprobe *p)
                printk("Cannot register a kprobe on rfid or mtmsrd\n");
                ret = -EINVAL;
        }
+
+       /* insn must be on a special executable page on ppc64 */
+       if (!ret) {
+               up(&kprobe_mutex);
+               p->ainsn.insn = get_insn_slot();
+               down(&kprobe_mutex);
+               if (!p->ainsn.insn)
+                       ret = -ENOMEM;
+       }
        return ret;
 }
 
@@ -79,16 +90,22 @@ void arch_disarm_kprobe(struct kprobe *p)
 
 void arch_remove_kprobe(struct kprobe *p)
 {
+       up(&kprobe_mutex);
+       free_insn_slot(p->ainsn.insn);
+       down(&kprobe_mutex);
 }
 
 static inline void prepare_singlestep(struct kprobe *p, struct pt_regs *regs)
 {
+       kprobe_opcode_t insn = *p->ainsn.insn;
+
        regs->msr |= MSR_SE;
-       /*single step inline if it a breakpoint instruction*/
-       if (p->opcode == BREAKPOINT_INSTRUCTION)
+
+       /* single step inline if it is a trap variant */
+       if (IS_TW(insn) || IS_TD(insn) || IS_TWI(insn) || IS_TDI(insn))
                regs->nip = (unsigned long)p->addr;
        else
-               regs->nip = (unsigned long)&p->ainsn.insn;
+               regs->nip = (unsigned long)p->ainsn.insn;
 }
 
 static inline void save_previous_kprobe(void)
@@ -205,9 +222,10 @@ no_kprobe:
 static void resume_execution(struct kprobe *p, struct pt_regs *regs)
 {
        int ret;
+       unsigned int insn = *p->ainsn.insn;
 
        regs->nip = (unsigned long)p->addr;
-       ret = emulate_step(regs, p->ainsn.insn[0]);
+       ret = emulate_step(regs, insn);
        if (ret == 0)
                regs->nip = (unsigned long)p->addr + 4;
 }
index 4e680f8..6a1c883 100644 (file)
@@ -38,7 +38,7 @@
 #include <linux/string.h>
 #include <linux/slab.h>
 #include <linux/preempt.h>
-#include <linux/moduleloader.h>
+
 #include <asm/cacheflush.h>
 #include <asm/pgtable.h>
 #include <asm/kdebug.h>
@@ -51,8 +51,6 @@ static struct kprobe *kprobe_prev;
 static unsigned long kprobe_status_prev, kprobe_old_rflags_prev, kprobe_saved_rflags_prev;
 static struct pt_regs jprobe_saved_regs;
 static long *jprobe_saved_rsp;
-static kprobe_opcode_t *get_insn_slot(void);
-static void free_insn_slot(kprobe_opcode_t *slot);
 void jprobe_return_end(void);
 
 /* copy of the kernel stack at the probe fire time */
@@ -681,112 +679,3 @@ int longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
        }
        return 0;
 }
-
-/*
- * kprobe->ainsn.insn points to the copy of the instruction to be single-stepped.
- * By default on x86_64, pages we get from kmalloc or vmalloc are not
- * executable.  Single-stepping an instruction on such a page yields an
- * oops.  So instead of storing the instruction copies in their respective
- * kprobe objects, we allocate a page, map it executable, and store all the
- * instruction copies there.  (We can allocate additional pages if somebody
- * inserts a huge number of probes.)  Each page can hold up to INSNS_PER_PAGE
- * instruction slots, each of which is MAX_INSN_SIZE*sizeof(kprobe_opcode_t)
- * bytes.
- */
-#define INSNS_PER_PAGE (PAGE_SIZE/(MAX_INSN_SIZE*sizeof(kprobe_opcode_t)))
-struct kprobe_insn_page {
-       struct hlist_node hlist;
-       kprobe_opcode_t *insns;         /* page of instruction slots */
-       char slot_used[INSNS_PER_PAGE];
-       int nused;
-};
-
-static struct hlist_head kprobe_insn_pages;
-
-/**
- * get_insn_slot() - Find a slot on an executable page for an instruction.
- * We allocate an executable page if there's no room on existing ones.
- */
-static kprobe_opcode_t *get_insn_slot(void)
-{
-       struct kprobe_insn_page *kip;
-       struct hlist_node *pos;
-
-       hlist_for_each(pos, &kprobe_insn_pages) {
-               kip = hlist_entry(pos, struct kprobe_insn_page, hlist);
-               if (kip->nused < INSNS_PER_PAGE) {
-                       int i;
-                       for (i = 0; i < INSNS_PER_PAGE; i++) {
-                               if (!kip->slot_used[i]) {
-                                       kip->slot_used[i] = 1;
-                                       kip->nused++;
-                                       return kip->insns + (i*MAX_INSN_SIZE);
-                               }
-                       }
-                       /* Surprise!  No unused slots.  Fix kip->nused. */
-                       kip->nused = INSNS_PER_PAGE;
-               }
-       }
-
-       /* All out of space.  Need to allocate a new page. Use slot 0.*/
-       kip = kmalloc(sizeof(struct kprobe_insn_page), GFP_KERNEL);
-       if (!kip) {
-               return NULL;
-       }
-
-       /*
-        * For the %rip-relative displacement fixups to be doable, we
-        * need our instruction copy to be within +/- 2GB of any data it
-        * might access via %rip.  That is, within 2GB of where the
-        * kernel image and loaded module images reside.  So we allocate
-        * a page in the module loading area.
-        */
-       kip->insns = module_alloc(PAGE_SIZE);
-       if (!kip->insns) {
-               kfree(kip);
-               return NULL;
-       }
-       INIT_HLIST_NODE(&kip->hlist);
-       hlist_add_head(&kip->hlist, &kprobe_insn_pages);
-       memset(kip->slot_used, 0, INSNS_PER_PAGE);
-       kip->slot_used[0] = 1;
-       kip->nused = 1;
-       return kip->insns;
-}
-
-/**
- * free_insn_slot() - Free instruction slot obtained from get_insn_slot().
- */
-static void free_insn_slot(kprobe_opcode_t *slot)
-{
-       struct kprobe_insn_page *kip;
-       struct hlist_node *pos;
-
-       hlist_for_each(pos, &kprobe_insn_pages) {
-               kip = hlist_entry(pos, struct kprobe_insn_page, hlist);
-               if (kip->insns <= slot
-                   && slot < kip->insns+(INSNS_PER_PAGE*MAX_INSN_SIZE)) {
-                       int i = (slot - kip->insns) / MAX_INSN_SIZE;
-                       kip->slot_used[i] = 0;
-                       kip->nused--;
-                       if (kip->nused == 0) {
-                               /*
-                                * Page is no longer in use.  Free it unless
-                                * it's the last one.  We keep the last one
-                                * so as not to have to set it up again the
-                                * next time somebody inserts a probe.
-                                */
-                               hlist_del(&kip->hlist);
-                               if (hlist_empty(&kprobe_insn_pages)) {
-                                       INIT_HLIST_NODE(&kip->hlist);
-                                       hlist_add_head(&kip->hlist,
-                                               &kprobe_insn_pages);
-                               } else {
-                                       module_free(NULL, kip->insns);
-                                       kfree(kip);
-                               }
-                       }
-                       return;
-               }
-       }
-}
index 7b70003..25d8b1e 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/ptrace.h>
 #include <asm/break.h>
 
+#define MAX_INSN_SIZE   16
 #define BREAK_INST     (long)(__IA64_BREAK_KPROBE << 6)
 
 typedef union cmp_inst {
index 19b468b..790cf7c 100644 (file)
@@ -45,7 +45,7 @@ typedef unsigned int kprobe_opcode_t;
 /* Architecture specific copy of original instruction */
 struct arch_specific_insn {
        /* copy of original instruction */
-       kprobe_opcode_t insn[MAX_INSN_SIZE];
+       kprobe_opcode_t *insn;
 };
 
 #ifdef CONFIG_KPROBES
index 5e1a7b0..d304d45 100644 (file)
@@ -177,6 +177,8 @@ extern void arch_arm_kprobe(struct kprobe *p);
 extern void arch_disarm_kprobe(struct kprobe *p);
 extern void arch_remove_kprobe(struct kprobe *p);
 extern void show_registers(struct pt_regs *regs);
+extern kprobe_opcode_t *get_insn_slot(void);
+extern void free_insn_slot(kprobe_opcode_t *slot);
 
 /* Get the kprobe at this addr (if any).  Must have called lock_kprobes */
 struct kprobe *get_kprobe(void *addr);
index 334f374..6524252 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/hash.h>
 #include <linux/init.h>
 #include <linux/module.h>
+#include <linux/moduleloader.h>
 #include <asm/cacheflush.h>
 #include <asm/errno.h>
 #include <asm/kdebug.h>
@@ -50,6 +51,106 @@ unsigned int kprobe_cpu = NR_CPUS;
 static DEFINE_SPINLOCK(kprobe_lock);
 static struct kprobe *curr_kprobe;
 
+/*
+ * kprobe->ainsn.insn points to the copy of the instruction to be
+ * single-stepped. x86_64, POWER4 and above have no-exec support and
+ * stepping on the instruction on a vmalloced/kmalloced/data page
+ * is a recipe for disaster
+ */
+#define INSNS_PER_PAGE (PAGE_SIZE/(MAX_INSN_SIZE * sizeof(kprobe_opcode_t)))
+
+struct kprobe_insn_page {
+       struct hlist_node hlist;
+       kprobe_opcode_t *insns;         /* Page of instruction slots */
+       char slot_used[INSNS_PER_PAGE];
+       int nused;
+};
+
+static struct hlist_head kprobe_insn_pages;
+
+/**
+ * get_insn_slot() - Find a slot on an executable page for an instruction.
+ * We allocate an executable page if there's no room on existing ones.
+ */
+kprobe_opcode_t *get_insn_slot(void)
+{
+       struct kprobe_insn_page *kip;
+       struct hlist_node *pos;
+
+       hlist_for_each(pos, &kprobe_insn_pages) {
+               kip = hlist_entry(pos, struct kprobe_insn_page, hlist);
+               if (kip->nused < INSNS_PER_PAGE) {
+                       int i;
+                       for (i = 0; i < INSNS_PER_PAGE; i++) {
+                               if (!kip->slot_used[i]) {
+                                       kip->slot_used[i] = 1;
+                                       kip->nused++;
+                                       return kip->insns + (i * MAX_INSN_SIZE);
+                               }
+                       }
+                       /* Surprise!  No unused slots.  Fix kip->nused. */
+                       kip->nused = INSNS_PER_PAGE;
+               }
+       }
+
+       /* All out of space.  Need to allocate a new page. Use slot 0.*/
+       kip = kmalloc(sizeof(struct kprobe_insn_page), GFP_KERNEL);
+       if (!kip) {
+               return NULL;
+       }
+
+       /*
+        * Use module_alloc so this page is within +/- 2GB of where the
+        * kernel image and loaded module images reside. This is required
+        * so x86_64 can correctly handle the %rip-relative fixups.
+        */
+       kip->insns = module_alloc(PAGE_SIZE);
+       if (!kip->insns) {
+               kfree(kip);
+               return NULL;
+       }
+       INIT_HLIST_NODE(&kip->hlist);
+       hlist_add_head(&kip->hlist, &kprobe_insn_pages);
+       memset(kip->slot_used, 0, INSNS_PER_PAGE);
+       kip->slot_used[0] = 1;
+       kip->nused = 1;
+       return kip->insns;
+}
+
+void free_insn_slot(kprobe_opcode_t *slot)
+{
+       struct kprobe_insn_page *kip;
+       struct hlist_node *pos;
+
+       hlist_for_each(pos, &kprobe_insn_pages) {
+               kip = hlist_entry(pos, struct kprobe_insn_page, hlist);
+               if (kip->insns <= slot &&
+                   slot < kip->insns + (INSNS_PER_PAGE * MAX_INSN_SIZE)) {
+                       int i = (slot - kip->insns) / MAX_INSN_SIZE;
+                       kip->slot_used[i] = 0;
+                       kip->nused--;
+                       if (kip->nused == 0) {
+                               /*
+                                * Page is no longer in use.  Free it unless
+                                * it's the last one.  We keep the last one
+                                * so as not to have to set it up again the
+                                * next time somebody inserts a probe.
+                                */
+                               hlist_del(&kip->hlist);
+                               if (hlist_empty(&kprobe_insn_pages)) {
+                                       INIT_HLIST_NODE(&kip->hlist);
+                                       hlist_add_head(&kip->hlist,
+                                               &kprobe_insn_pages);
+                               } else {
+                                       module_free(NULL, kip->insns);
+                                       kfree(kip);
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
 /* Locks kprobe: irqs must be disabled */
 void lock_kprobes(void)
 {