Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9ec4b1f3 authored by Ananth N Mavinakayanahalli's avatar Ananth N Mavinakayanahalli Committed by Linus Torvalds
Browse files

[PATCH] kprobes: fix single-step out of line - take2



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: default avatarAnanth N Mavinakayanahalli <ananth@in.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent d3b8a1a8
Loading
Loading
Loading
Loading
+22 −4
Original line number Diff line number Diff line
@@ -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 @@ static inline int kprobe_handler(struct pt_regs *regs)
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;
}
+1 −112
Original line number Diff line number Diff line
@@ -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;
		}
	}
}
+1 −0
Original line number Diff line number Diff line
@@ -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 {
+1 −1
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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);
Loading