Skip to content

Commit

Permalink
[PATCH] kprobes: Allow multiple kprobes at the same address
Browse files Browse the repository at this point in the history
Allow registration of multiple kprobes at an address in an architecture
agnostic way.  Corresponding handlers will be invoked in a sequence.  But,
a kprobe and a jprobe can't (yet) co-exist at the same address.

Signed-off-by: Ananth N Mavinakayanahalli <amavin@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
  • Loading branch information
Ananth N Mavinakayanahalli authored and Linus Torvalds committed May 5, 2005
1 parent 04dea5f commit 64f562c
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 13 deletions.
3 changes: 3 additions & 0 deletions include/linux/kprobes.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ typedef int (*kprobe_fault_handler_t) (struct kprobe *, struct pt_regs *,
struct kprobe {
struct hlist_node hlist;

/* list of kprobes for multi-handler support */
struct list_head list;

/* location of the probe point */
kprobe_opcode_t *addr;

Expand Down
144 changes: 131 additions & 13 deletions kernel/kprobes.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE];

unsigned int kprobe_cpu = NR_CPUS;
static DEFINE_SPINLOCK(kprobe_lock);
static struct kprobe *curr_kprobe;

/* Locks kprobe: irqs must be disabled */
void lock_kprobes(void)
Expand Down Expand Up @@ -73,22 +74,139 @@ struct kprobe *get_kprobe(void *addr)
return NULL;
}

/*
* Aggregate handlers for multiple kprobes support - these handlers
* take care of invoking the individual kprobe handlers on p->list
*/
int aggr_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
struct kprobe *kp;

list_for_each_entry(kp, &p->list, list) {
if (kp->pre_handler) {
curr_kprobe = kp;
kp->pre_handler(kp, regs);
curr_kprobe = NULL;
}
}
return 0;
}

void aggr_post_handler(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
struct kprobe *kp;

list_for_each_entry(kp, &p->list, list) {
if (kp->post_handler) {
curr_kprobe = kp;
kp->post_handler(kp, regs, flags);
curr_kprobe = NULL;
}
}
return;
}

int aggr_fault_handler(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
/*
* if we faulted "during" the execution of a user specified
* probe handler, invoke just that probe's fault handler
*/
if (curr_kprobe && curr_kprobe->fault_handler) {
if (curr_kprobe->fault_handler(curr_kprobe, regs, trapnr))
return 1;
}
return 0;
}

/*
* Fill in the required fields of the "manager kprobe". Replace the
* earlier kprobe in the hlist with the manager kprobe
*/
static inline void add_aggr_kprobe(struct kprobe *ap, struct kprobe *p)
{
ap->addr = p->addr;
ap->opcode = p->opcode;
memcpy(&ap->ainsn, &p->ainsn, sizeof(struct arch_specific_insn));

ap->pre_handler = aggr_pre_handler;
ap->post_handler = aggr_post_handler;
ap->fault_handler = aggr_fault_handler;

INIT_LIST_HEAD(&ap->list);
list_add(&p->list, &ap->list);

INIT_HLIST_NODE(&ap->hlist);
hlist_del(&p->hlist);
hlist_add_head(&ap->hlist,
&kprobe_table[hash_ptr(ap->addr, KPROBE_HASH_BITS)]);
}

/*
* This is the second or subsequent kprobe at the address - handle
* the intricacies
* TODO: Move kcalloc outside the spinlock
*/
static int register_aggr_kprobe(struct kprobe *old_p, struct kprobe *p)
{
int ret = 0;
struct kprobe *ap;

if (old_p->break_handler || p->break_handler) {
ret = -EEXIST; /* kprobe and jprobe can't (yet) coexist */
} else if (old_p->pre_handler == aggr_pre_handler) {
list_add(&p->list, &old_p->list);
} else {
ap = kcalloc(1, sizeof(struct kprobe), GFP_ATOMIC);
if (!ap)
return -ENOMEM;
add_aggr_kprobe(ap, old_p);
list_add(&p->list, &ap->list);
}
return ret;
}

/* kprobe removal house-keeping routines */
static inline void cleanup_kprobe(struct kprobe *p, unsigned long flags)
{
*p->addr = p->opcode;
hlist_del(&p->hlist);
flush_icache_range((unsigned long) p->addr,
(unsigned long) p->addr + sizeof(kprobe_opcode_t));
spin_unlock_irqrestore(&kprobe_lock, flags);
arch_remove_kprobe(p);
}

static inline void cleanup_aggr_kprobe(struct kprobe *old_p,
struct kprobe *p, unsigned long flags)
{
list_del(&p->list);
if (list_empty(&old_p->list)) {
cleanup_kprobe(old_p, flags);
kfree(old_p);
} else
spin_unlock_irqrestore(&kprobe_lock, flags);
}

int register_kprobe(struct kprobe *p)
{
int ret = 0;
unsigned long flags = 0;
struct kprobe *old_p;

if ((ret = arch_prepare_kprobe(p)) != 0) {
goto rm_kprobe;
}
spin_lock_irqsave(&kprobe_lock, flags);
INIT_HLIST_NODE(&p->hlist);
if (get_kprobe(p->addr)) {
ret = -EEXIST;
old_p = get_kprobe(p->addr);
if (old_p) {
ret = register_aggr_kprobe(old_p, p);
goto out;
}
arch_copy_kprobe(p);

arch_copy_kprobe(p);
INIT_HLIST_NODE(&p->hlist);
hlist_add_head(&p->hlist,
&kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);

Expand All @@ -107,17 +225,17 @@ int register_kprobe(struct kprobe *p)
void unregister_kprobe(struct kprobe *p)
{
unsigned long flags;
struct kprobe *old_p;

spin_lock_irqsave(&kprobe_lock, flags);
if (!get_kprobe(p->addr)) {
old_p = get_kprobe(p->addr);
if (old_p) {
if (old_p->pre_handler == aggr_pre_handler)
cleanup_aggr_kprobe(old_p, p, flags);
else
cleanup_kprobe(p, flags);
} else
spin_unlock_irqrestore(&kprobe_lock, flags);
return;
}
*p->addr = p->opcode;
hlist_del(&p->hlist);
flush_icache_range((unsigned long) p->addr,
(unsigned long) p->addr + sizeof(kprobe_opcode_t));
spin_unlock_irqrestore(&kprobe_lock, flags);
arch_remove_kprobe(p);
}

static struct notifier_block kprobe_exceptions_nb = {
Expand Down

0 comments on commit 64f562c

Please sign in to comment.