This is the mail archive of the systemtap@sourceware.org mailing list for the systemtap project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH 2/5][djprobe] djprobe core patch


Hi,

This patch is the architecture independent part of the djprobe.
In this patch, both of jump inserting and buffer removing are
executed by the commit_djprobes() batch function. This batch
function freezes processes while inserting jumps and removing
buffers on the preemptible kernel. So we can do it safely.
However, the handlers of djprobes are activated/deactivated right
after calling register/unregister_djprobe() functions. Before calling
commit_djprobes(), these handlers are invoked by normal kprobes.

I also updated API reference.

API Reference
-------------
The Djprobe API includes "register_djprobe" function,
"unregister_djprobe" function and "commit_djprobes" function.
Here are specifications for these functions and the
associated probe handlers.

1. register_djprobe

#include <linux/djprobe.h>
int register_djprobe(struct djprobe *djp);

Registers djprobe at the specified address.
When the kernel hits the address, Djprobe calls djp->handler.

register_djprobe() returns 0 on success, or a negative errno otherwise.

User's probe handler (djp->handler):
#include <linux/djprobe.h>
#include <linux/ptrace.h>
void handler(struct djprobe *djp, struct pt_regs *regs);

Called with p pointing to the djprobe associated with the probe point,
and regs pointing to the struct containing the registers saved when
the probe point was hit.

2. unregister_djprobe

#include <linux/djprobe.h>
void unregister_djprobe(struct djprobe *djp);

Removes the specified probe handler.  The unregister function can
be called at anytime after the probe has been registered.

3. commit_djprobes

#include <linux/djprobe.h>
int commit_djprobes(void);

Inserts jump instruction of registered probes and releases buffers
of unregistered probes. The commit function can be called at anytime.

commit_djprobes() returns 0 on success, or a negative errno otherwise.

-----

Thanks,

-- 
Masami HIRAMATSU
Linux Technology Center
Hitachi, Ltd., Systems Development Laboratory
E-mail: masami.hiramatsu.pt@hitachi.com

---
 include/linux/djprobe.h |   89 ++++++++++++++
 include/linux/kprobes.h |    5
 kernel/Makefile         |    1
 kernel/djprobe.c        |  303 ++++++++++++++++++++++++++++++++++++++++++++++++
 kernel/kprobes.c        |   18 ++
 5 files changed, 415 insertions(+), 1 deletion(-)
Index: linux-2.6.19-rc1-mm1/include/linux/djprobe.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.19-rc1-mm1/include/linux/djprobe.h	2006-10-16 22:06:22.000000000 +0900
@@ -0,0 +1,89 @@
+#ifndef _LINUX_DJPROBE_H
+#define _LINUX_DJPROBE_H
+/*
+ *  Kernel Direct Jump Probe (Djprobe)
+ *  include/linux/djprobe.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) Hitachi, Ltd. 2005,2006
+ *
+ * 2005-Aug	Created by Masami HIRAMATSU <masami.hiramatsu.pt@hitachi.com>
+ * 		Initial implementation of Direct jump probe (djprobe)
+ *              to reduce overhead.
+ */
+#include <linux/autoconf.h>
+#include <linux/list.h>
+#include <linux/smp.h>
+#include <linux/kprobes.h>
+#include <asm/djprobe.h>
+
+struct djprobe;
+/* djprobe's instance (internal use)*/
+struct djprobe_instance {
+	struct list_head plist; /* list of djprobes for multiprobe support */
+	struct arch_djprobe_stub stub;
+	struct kprobe kp;
+	struct hlist_node hlist; /* list of djprobe_instances */
+	struct list_head rlist;
+};
+
+struct djprobe;
+typedef void (*djprobe_handler_t) (struct djprobe *, struct pt_regs *);
+/*
+ * Direct Jump probe interface structure
+ */
+struct djprobe {
+	struct list_head plist;		/* list of djprobes */
+	djprobe_handler_t handler;	/* probing handler (pre-executed) */
+	struct djprobe_instance *inst;	/* pointer for instance */
+	struct arch_djprobe_param param;	/* arch dependent parameter */
+};
+
+#ifdef CONFIG_DJPROBE
+#define __ARCH_WANT_KPROBES_INSN_SLOT
+extern struct mutex djprobe_mutex;
+struct djprobe_instance * __get_djprobe_instance(void *addr, int size);
+
+/* architecture dependent functions */
+extern int arch_prepare_djprobe_instance(struct djprobe_instance *djpi,
+					 struct arch_djprobe_param *param);
+extern void arch_install_djprobe_instance(struct djprobe_instance *djpi);
+extern void arch_post_install_djprobe_instance(struct djprobe_instance *djpi);
+extern void arch_pre_remove_djprobe_instance(struct djprobe_instance *djpi);
+extern void arch_remove_djprobe_instance(struct djprobe_instance *djpi);
+extern void arch_serialize_cpus(void);
+extern int arch_switch_to_stub(struct djprobe_instance *djpi,
+			       struct pt_regs * regs);
+extern int djprobe_kprobe(struct kprobe *kp);
+
+/* user interfaces */
+int register_djprobe(struct djprobe *p);
+void unregister_djprobe(struct djprobe *p);
+int commit_djprobes(void);
+#else /* CONFIG_DJPROBE */
+static inline int register_djprobe(struct djprobe *p)
+{
+	return -ENOSYS;
+}
+static inline int commit_djprobes(void)
+{
+	return -ENOSYS;
+}
+static inline void unregister_djprobe(struct djprobe *p)
+{
+}
+#endif				/* CONFIG_DJPROBE */
+#endif				/* _LINUX_DJPROBE_H */
Index: linux-2.6.19-rc1-mm1/include/linux/kprobes.h
===================================================================
--- linux-2.6.19-rc1-mm1.orig/include/linux/kprobes.h	2006-10-16 22:04:37.000000000 +0900
+++ linux-2.6.19-rc1-mm1/include/linux/kprobes.h	2006-10-16 22:06:22.000000000 +0900
@@ -173,6 +173,11 @@
 extern kprobe_opcode_t *get_insn_slot(void);
 extern void free_insn_slot(kprobe_opcode_t *slot, int dirty);
 extern void kprobes_inc_nmissed_count(struct kprobe *p);
+extern kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_page_list *pages);
+extern void __free_insn_slot(struct kprobe_insn_page_list *pages,
+			     kprobe_opcode_t * slot, int dirty);
+extern int in_kprobes_functions(unsigned long addr);
+

 /* Get the kprobe at this addr (if any) - called with preemption disabled */
 struct kprobe *get_kprobe(void *addr);
Index: linux-2.6.19-rc1-mm1/kernel/Makefile
===================================================================
--- linux-2.6.19-rc1-mm1.orig/kernel/Makefile	2006-10-16 21:43:03.000000000 +0900
+++ linux-2.6.19-rc1-mm1/kernel/Makefile	2006-10-16 22:06:22.000000000 +0900
@@ -42,6 +42,7 @@
 obj-$(CONFIG_AUDIT) += audit.o auditfilter.o
 obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
 obj-$(CONFIG_KPROBES) += kprobes.o
+obj-$(CONFIG_DJPROBE) += djprobe.o
 obj-$(CONFIG_SYSFS) += ksysfs.o
 obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
Index: linux-2.6.19-rc1-mm1/kernel/djprobe.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.19-rc1-mm1/kernel/djprobe.c	2006-10-16 22:06:22.000000000 +0900
@@ -0,0 +1,303 @@
+/*
+ *  Kernel Direct Jump Probe (Djprobe)
+ *  kernel/djprobes.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) Hitachi, Ltd. 2005,2006
+ *
+ * 2005-Aug	Created by Masami HIRAMATSU <masami.hiramatsu.pt@hitachi.com>
+ * 		Initial implementation of Direct jump probe (djprobe)
+ *              to reduce overhead.
+ * 2006-Sep	add preemptive kernel support.
+ */
+#include <linux/djprobe.h>
+#include <linux/hash.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleloader.h>
+#include <linux/sched.h>
+#include <asm-generic/sections.h>
+#include <asm/errno.h>
+#include <linux/hardirq.h>
+#include <linux/cpu.h>
+#include <linux/percpu.h>
+#include <linux/mutex.h>
+
+/*
+ * The djprobe do not refer instances list when probe function called.
+ * This list is operated on registering and unregistering djprobe.
+ */
+#define DJPROBE_BLOCK_BITS 6
+#define DJPROBE_BLOCK_SIZE (1 << DJPROBE_BLOCK_BITS)
+#define DJPROBE_HASH_BITS 8
+#define DJPROBE_TABLE_SIZE (1 << DJPROBE_HASH_BITS)
+#define DJPROBE_TABLE_MASK (DJPROBE_TABLE_SIZE - 1)
+
+/* djprobe instance hash table */
+static struct hlist_head djprobe_inst_table[DJPROBE_TABLE_SIZE];
+
+#define hash_djprobe(key) \
+	(((unsigned long)(key) >> DJPROBE_BLOCK_BITS) & DJPROBE_TABLE_MASK)
+
+DEFINE_MUTEX(djprobe_mutex);
+
+/* Instruction pages for djprobe's stub code */
+static struct kprobe_insn_page_list djprobe_insn_pages = {
+	.list = HLIST_HEAD_INIT,
+	.insn_size = 0,
+	.nr_garbage = 0
+};
+
+static LIST_HEAD(registering_list);
+static LIST_HEAD(unregistering_list);
+
+static int __djprobe_pre_handler(struct kprobe *kp, struct pt_regs *regs)
+{
+	struct djprobe_instance *djpi =
+	    container_of(kp, struct djprobe_instance, kp);
+
+	return arch_switch_to_stub(djpi, regs);
+}
+
+int __kprobes djprobe_kprobe(struct kprobe *kp)
+{
+	return kp->pre_handler == __djprobe_pre_handler;
+}
+
+static void __free_djprobe_instance(struct djprobe_instance *djpi)
+{
+	hlist_del(&djpi->hlist);
+	if (djpi->kp.addr) {
+		unregister_kprobe(&(djpi->kp));	/* including safety check */
+	}
+	if (djpi->stub.insn)
+		__free_insn_slot(&djprobe_insn_pages, djpi->stub.insn, 0);
+	kfree(djpi);
+}
+
+static __always_inline
+    struct djprobe_instance *__create_djprobe_instance(struct arch_djprobe_param
+						       *param)
+{
+	struct djprobe_instance *djpi;
+	void * addr = (void *)djprobe_param_address(param);
+	/* allocate a new instance */
+	djpi = kcalloc(1, sizeof(struct djprobe_instance), GFP_ATOMIC);
+	if (djpi == NULL) {
+		goto out;
+	}
+
+	/* allocate stub */
+	djpi->stub.insn = __get_insn_slot(&djprobe_insn_pages);
+	if (djpi->stub.insn == NULL) {
+		__free_djprobe_instance(djpi);
+		djpi = NULL;
+		goto out;
+	}
+
+	arch_prepare_djprobe_instance(djpi, param);
+
+	INIT_LIST_HEAD(&djpi->plist);
+	INIT_LIST_HEAD(&djpi->rlist);
+	INIT_HLIST_NODE(&djpi->hlist);
+	hlist_add_head(&djpi->hlist,
+		       &djprobe_inst_table[hash_djprobe(addr)]);
+
+	/* register as a kprobe */
+	djpi->kp.addr = addr;
+	djpi->kp.pre_handler = __djprobe_pre_handler;
+	if (register_kprobe(&djpi->kp) < 0) {
+		djpi->kp.addr = NULL;
+		__free_djprobe_instance(djpi);
+		djpi = NULL;
+	} else {
+		list_add(&djpi->rlist, &registering_list);
+	}
+
+      out:
+	return djpi;
+}
+
+struct djprobe_instance *__kprobes __get_djprobe_instance(void *addr, int size)
+{
+	struct djprobe_instance *djpi;
+	struct hlist_node *node;
+	unsigned long idx, eidx;
+
+	idx = hash_djprobe(addr - ARCH_STUB_INSN_MAX);
+	eidx = ((hash_djprobe(addr + size) + 1) & DJPROBE_TABLE_MASK);
+	do {
+		hlist_for_each_entry(djpi, node, &djprobe_inst_table[idx],
+				     hlist) {
+			if (((long)addr <
+			     (long)djpi->kp.addr + DJPI_ARCH_SIZE(djpi))
+			    && ((long)djpi->kp.addr < (long)addr + size)) {
+				return djpi;
+			}
+		}
+		idx = ((idx + 1) & DJPROBE_TABLE_MASK);
+	} while (idx != eidx);
+
+	return NULL;
+}
+
+/* make sure calling with locking djprobe_mutex */
+static int __commit_djprobes(void)
+{
+	struct djprobe_instance *djpi;
+	int ret = -EAGAIN;
+
+	if (list_empty(&registering_list) &&
+	    list_empty(&unregistering_list)) {
+		return 0;
+	}
+
+#ifdef CONFIG_PREEMPT
+	if (freeze_processes() != 0)
+		goto out;
+#endif
+	/* remove jump code */
+	if (!list_empty(&unregistering_list)) {
+		list_for_each_entry(djpi, &unregistering_list, rlist) {
+			arch_remove_djprobe_instance(djpi);
+		}
+		arch_serialize_cpus();
+	}
+
+	/* ensure safety */
+	synchronize_sched();
+
+	/* inserting jump code */
+	if (!list_empty(&registering_list)) {
+		list_for_each_entry(djpi, &registering_list, rlist) {
+			arch_install_djprobe_instance(djpi);
+		}
+		arch_serialize_cpus();
+		while (!list_empty(&registering_list)) {
+			djpi = list_entry(registering_list.next,
+					  struct djprobe_instance, rlist);
+			list_del_init(&djpi->rlist);
+			arch_post_install_djprobe_instance(djpi);
+		}
+	}
+	/* release code buffer */
+	while (!list_empty(&unregistering_list)) {
+		djpi = list_entry(unregistering_list.next,
+				  struct djprobe_instance, rlist);
+		list_del(&djpi->rlist);
+		__free_djprobe_instance(djpi);
+	}
+	ret = 0;
+
+#ifdef CONFIG_PREEMPT
+      out:
+	thaw_processes();
+#endif
+	return ret;
+}
+
+int __kprobes commit_djprobes(void)
+{
+	int ret;
+	BUG_ON(in_interrupt());
+	mutex_lock(&djprobe_mutex);
+	ret = __commit_djprobes();
+	mutex_unlock(&djprobe_mutex);
+	return ret;
+}
+
+int __kprobes register_djprobe(struct djprobe *djp)
+{
+	struct djprobe_instance *djpi;
+	int ret = 0, i;
+	void * addr = (void *)djprobe_param_address(&djp->param);
+	unsigned long len = (unsigned long)djprobe_param_length(&djp->param);
+
+	BUG_ON(in_interrupt());
+
+	if (len > ARCH_STUB_INSN_MAX || len < ARCH_STUB_INSN_MIN)
+		return -EINVAL;
+
+	if ((ret = in_kprobes_functions((long)addr)) != 0)
+		return ret;
+
+	mutex_lock(&djprobe_mutex);
+
+	/* check confliction with other djprobes */
+	djpi = __get_djprobe_instance(addr, len);
+	if (djpi) {
+		if (djpi->kp.addr == addr) {
+			djp->inst = djpi;	/* add to another instance */
+			list_add_rcu(&djp->plist, &djpi->plist);
+		} else {
+			ret = -EEXIST;	/* other djprobes were inserted */
+		}
+		goto out;
+	}
+	/* check confliction with kprobes */
+	for (i = 1; i < len; i++) {
+		if (get_kprobe((void *)((long)addr + i))) {
+			ret = -EEXIST;	/* a kprobes were inserted */
+			goto out;
+		}
+	}
+
+	djpi = __create_djprobe_instance(&djp->param);
+	if (djpi == NULL) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/* attach */
+	djp->inst = djpi;
+	INIT_LIST_HEAD(&djp->plist);
+	list_add_rcu(&djp->plist, &djpi->plist);
+
+      out:
+	mutex_unlock(&djprobe_mutex);
+	return ret;
+}
+
+void __kprobes unregister_djprobe(struct djprobe *djp)
+{
+	struct djprobe_instance *djpi;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&djprobe_mutex);
+	djpi = djp->inst;
+	list_del_rcu(&djp->plist);
+	djp->inst = NULL;
+	if (list_empty(&djpi->plist)) {
+		if (!list_empty(&djpi->rlist)) /* this is not committed yet */
+			list_del_init(&djpi->rlist);
+		arch_pre_remove_djprobe_instance(djpi);
+		list_add(&djpi->rlist, &unregistering_list);
+	}
+	mutex_unlock(&djprobe_mutex);
+}
+
+static int __init init_djprobe(void)
+{
+	djprobe_insn_pages.insn_size = ARCH_STUB_SIZE;
+	return 0;
+}
+
+__initcall(init_djprobe);
+
+EXPORT_SYMBOL_GPL(commit_djprobes);
+EXPORT_SYMBOL_GPL(register_djprobe);
+EXPORT_SYMBOL_GPL(unregister_djprobe);
Index: linux-2.6.19-rc1-mm1/kernel/kprobes.c
===================================================================
--- linux-2.6.19-rc1-mm1.orig/kernel/kprobes.c	2006-10-16 22:04:22.000000000 +0900
+++ linux-2.6.19-rc1-mm1/kernel/kprobes.c	2006-10-16 22:06:22.000000000 +0900
@@ -39,6 +39,7 @@
 #include <linux/moduleloader.h>
 #include <linux/kallsyms.h>
 #include <linux/sched.h>
+#include <linux/djprobe.h>
 #include <asm-generic/sections.h>
 #include <asm/cacheflush.h>
 #include <asm/errno.h>
@@ -532,7 +533,7 @@
 	return ret;
 }

-static int __kprobes in_kprobes_functions(unsigned long addr)
+int __kprobes in_kprobes_functions(unsigned long addr)
 {
 	if (addr >= (unsigned long)__kprobes_text_start
 		&& addr < (unsigned long)__kprobes_text_end)
@@ -582,6 +583,16 @@
 			probed_mod = NULL;
 	}

+#ifdef CONFIG_DJPROBE
+	if (!djprobe_kprobe(p)) {
+		mutex_lock(&djprobe_mutex);
+		if (__get_djprobe_instance(p->addr, 1) != NULL) {
+			mutex_unlock(&djprobe_mutex);
+			return -EEXIST;
+		}
+	}
+#endif /* CONFIG_DJPROBE */
+
 	p->nmissed = 0;
 	mutex_lock(&kprobe_mutex);
 	old_p = get_kprobe(p->addr);
@@ -608,6 +619,11 @@
 out:
 	mutex_unlock(&kprobe_mutex);

+#ifdef CONFIG_DJPROBE
+	if (!djprobe_kprobe(p))
+		mutex_unlock(&djprobe_mutex);
+#endif /* CONFIG_DJPROBE */
+
 	if (ret && probed_mod)
 		module_put(probed_mod);
 	return ret;








Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]