mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 21:38:32 +08:00
[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: Ananth N Mavinakayanahalli <ananth@in.ibm.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
d3b8a1a849
commit
9ec4b1f356
@ -36,6 +36,8 @@
|
|||||||
#include <asm/kdebug.h>
|
#include <asm/kdebug.h>
|
||||||
#include <asm/sstep.h>
|
#include <asm/sstep.h>
|
||||||
|
|
||||||
|
static DECLARE_MUTEX(kprobe_mutex);
|
||||||
|
|
||||||
static struct kprobe *current_kprobe;
|
static struct kprobe *current_kprobe;
|
||||||
static unsigned long kprobe_status, kprobe_saved_msr;
|
static unsigned long kprobe_status, kprobe_saved_msr;
|
||||||
static struct kprobe *kprobe_prev;
|
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");
|
printk("Cannot register a kprobe on rfid or mtmsrd\n");
|
||||||
ret = -EINVAL;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,16 +90,22 @@ void arch_disarm_kprobe(struct kprobe *p)
|
|||||||
|
|
||||||
void arch_remove_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)
|
static inline void prepare_singlestep(struct kprobe *p, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
|
kprobe_opcode_t insn = *p->ainsn.insn;
|
||||||
|
|
||||||
regs->msr |= MSR_SE;
|
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;
|
regs->nip = (unsigned long)p->addr;
|
||||||
else
|
else
|
||||||
regs->nip = (unsigned long)&p->ainsn.insn;
|
regs->nip = (unsigned long)p->ainsn.insn;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void save_previous_kprobe(void)
|
static inline void save_previous_kprobe(void)
|
||||||
@ -205,9 +222,10 @@ no_kprobe:
|
|||||||
static void resume_execution(struct kprobe *p, struct pt_regs *regs)
|
static void resume_execution(struct kprobe *p, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
unsigned int insn = *p->ainsn.insn;
|
||||||
|
|
||||||
regs->nip = (unsigned long)p->addr;
|
regs->nip = (unsigned long)p->addr;
|
||||||
ret = emulate_step(regs, p->ainsn.insn[0]);
|
ret = emulate_step(regs, insn);
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
regs->nip = (unsigned long)p->addr + 4;
|
regs->nip = (unsigned long)p->addr + 4;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/preempt.h>
|
#include <linux/preempt.h>
|
||||||
#include <linux/moduleloader.h>
|
|
||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
#include <asm/pgtable.h>
|
#include <asm/pgtable.h>
|
||||||
#include <asm/kdebug.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 unsigned long kprobe_status_prev, kprobe_old_rflags_prev, kprobe_saved_rflags_prev;
|
||||||
static struct pt_regs jprobe_saved_regs;
|
static struct pt_regs jprobe_saved_regs;
|
||||||
static long *jprobe_saved_rsp;
|
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);
|
void jprobe_return_end(void);
|
||||||
|
|
||||||
/* copy of the kernel stack at the probe fire time */
|
/* 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;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include <linux/ptrace.h>
|
#include <linux/ptrace.h>
|
||||||
#include <asm/break.h>
|
#include <asm/break.h>
|
||||||
|
|
||||||
|
#define MAX_INSN_SIZE 16
|
||||||
#define BREAK_INST (long)(__IA64_BREAK_KPROBE << 6)
|
#define BREAK_INST (long)(__IA64_BREAK_KPROBE << 6)
|
||||||
|
|
||||||
typedef union cmp_inst {
|
typedef union cmp_inst {
|
||||||
|
@ -45,7 +45,7 @@ typedef unsigned int kprobe_opcode_t;
|
|||||||
/* Architecture specific copy of original instruction */
|
/* Architecture specific copy of original instruction */
|
||||||
struct arch_specific_insn {
|
struct arch_specific_insn {
|
||||||
/* copy of original instruction */
|
/* copy of original instruction */
|
||||||
kprobe_opcode_t insn[MAX_INSN_SIZE];
|
kprobe_opcode_t *insn;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_KPROBES
|
#ifdef CONFIG_KPROBES
|
||||||
|
@ -177,6 +177,8 @@ extern void arch_arm_kprobe(struct kprobe *p);
|
|||||||
extern void arch_disarm_kprobe(struct kprobe *p);
|
extern void arch_disarm_kprobe(struct kprobe *p);
|
||||||
extern void arch_remove_kprobe(struct kprobe *p);
|
extern void arch_remove_kprobe(struct kprobe *p);
|
||||||
extern void show_registers(struct pt_regs *regs);
|
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 */
|
/* Get the kprobe at this addr (if any). Must have called lock_kprobes */
|
||||||
struct kprobe *get_kprobe(void *addr);
|
struct kprobe *get_kprobe(void *addr);
|
||||||
|
101
kernel/kprobes.c
101
kernel/kprobes.c
@ -36,6 +36,7 @@
|
|||||||
#include <linux/hash.h>
|
#include <linux/hash.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/moduleloader.h>
|
||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
#include <asm/errno.h>
|
#include <asm/errno.h>
|
||||||
#include <asm/kdebug.h>
|
#include <asm/kdebug.h>
|
||||||
@ -50,6 +51,106 @@ unsigned int kprobe_cpu = NR_CPUS;
|
|||||||
static DEFINE_SPINLOCK(kprobe_lock);
|
static DEFINE_SPINLOCK(kprobe_lock);
|
||||||
static struct kprobe *curr_kprobe;
|
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 */
|
/* Locks kprobe: irqs must be disabled */
|
||||||
void lock_kprobes(void)
|
void lock_kprobes(void)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user