This is the mail archive of the
systemtap@sourceware.org
mailing list for the systemtap project.
[PATCH 2/5][djprobe] djprobe core patch
- From: Masami Hiramatsu <masami dot hiramatsu dot pt at hitachi dot com>
- To: "Keshavamurthy, Anil S" <anil dot s dot keshavamurthy at intel dot com>, Ananth N Mavinakayanahalli <ananth at in dot ibm dot com>, Prasanna S Panchamukhi <prasanna at in dot ibm dot com>, Ingo Molnar <mingo at redhat dot com>, SystemTAP <systemtap at sources dot redhat dot com>
- Cc: Masami Hiramatsu <masami dot hiramatsu dot pt at hitachi dot com>, Satoshi Oshima <soshima at redhat dot com>, Hideo Aoki <haoki at redhat dot com>, Yumiko Sugita <yumiko dot sugita dot yf at hitachi dot com>
- Date: Thu, 19 Oct 2006 18:02:32 +0900
- Subject: [PATCH 2/5][djprobe] djprobe core patch
- Organization: Systems Development Lab., Hitachi, Ltd., Japan
- References: <45338593.6090207@hitachi.com>
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, ®istering_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(®istering_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(®istering_list)) {
+ list_for_each_entry(djpi, ®istering_list, rlist) {
+ arch_install_djprobe_instance(djpi);
+ }
+ arch_serialize_cpus();
+ while (!list_empty(®istering_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;