diff options
| author | Ananth N Mavinakayanahalli <ananth@in.ibm.com> | 2008-01-30 13:32:53 +0100 | 
|---|---|---|
| committer | Ingo Molnar <mingo@elte.hu> | 2008-01-30 13:32:53 +0100 | 
| commit | 8c1c9356429741a82ff176d0f3400fb9e06b2a30 (patch) | |
| tree | 4daa7864163b77943e3d303c32a08672f443685e | |
| parent | 3334052a321aca0ffecb54244d666311f98f5487 (diff) | |
x86: kprobes: add kprobes smoke tests that run on boot
Here is a quick and naive smoke test for kprobes. This is intended to
just verify if some unrelated change broke the *probes subsystem. It is
self contained, architecture agnostic and isn't of any great use by itself.
This needs to be built in the kernel and runs a basic set of tests to
verify if kprobes, jprobes and kretprobes run fine on the kernel. In case
of an error, it'll print out a message with a "BUG" prefix.
This is a start; we intend to add more tests to this bucket over time.
Thanks to Jim Keniston and Masami Hiramatsu for comments and suggestions.
Tested on x86 (32/64) and powerpc.
Signed-off-by: Ananth N Mavinakayanahalli <ananth@in.ibm.com>
Acked-by: Masami Hiramatsu <mhiramat@redhat.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
| -rw-r--r-- | include/linux/kprobes.h | 10 | ||||
| -rw-r--r-- | kernel/Makefile | 1 | ||||
| -rw-r--r-- | kernel/kprobes.c | 2 | ||||
| -rw-r--r-- | kernel/test_kprobes.c | 216 | ||||
| -rw-r--r-- | lib/Kconfig.debug | 12 | 
5 files changed, 241 insertions, 0 deletions
| diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h index 81891581e89b..6168c0a44172 100644 --- a/include/linux/kprobes.h +++ b/include/linux/kprobes.h @@ -182,6 +182,15 @@ static inline void kretprobe_assert(struct kretprobe_instance *ri,  	}  } +#ifdef CONFIG_KPROBES_SANITY_TEST +extern int init_test_probes(void); +#else +static inline int init_test_probes(void) +{ +	return 0; +} +#endif /* CONFIG_KPROBES_SANITY_TEST */ +  extern spinlock_t kretprobe_lock;  extern struct mutex kprobe_mutex;  extern int arch_prepare_kprobe(struct kprobe *p); @@ -227,6 +236,7 @@ void unregister_kretprobe(struct kretprobe *rp);  void kprobe_flush_task(struct task_struct *tk);  void recycle_rp_inst(struct kretprobe_instance *ri, struct hlist_head *head); +  #else /* CONFIG_KPROBES */  #define __kprobes	/**/ diff --git a/kernel/Makefile b/kernel/Makefile index 390d42146267..62015c3d8d91 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_CPUSETS) += cpuset.o  obj-$(CONFIG_CGROUP_NS) += ns_cgroup.o  obj-$(CONFIG_IKCONFIG) += configs.o  obj-$(CONFIG_STOP_MACHINE) += stop_machine.o +obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o  obj-$(CONFIG_AUDIT) += audit.o auditfilter.o  obj-$(CONFIG_AUDITSYSCALL) += auditsc.o  obj-$(CONFIG_AUDIT_TREE) += audit_tree.o diff --git a/kernel/kprobes.c b/kernel/kprobes.c index e3a5d817ac9b..d0493eafea3e 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -824,6 +824,8 @@ static int __init init_kprobes(void)  	if (!err)  		err = register_die_notifier(&kprobe_exceptions_nb); +	if (!err) +		init_test_probes();  	return err;  } diff --git a/kernel/test_kprobes.c b/kernel/test_kprobes.c new file mode 100644 index 000000000000..88cdb109e13c --- /dev/null +++ b/kernel/test_kprobes.c @@ -0,0 +1,216 @@ +/* + * test_kprobes.c - simple sanity test for *probes + * + * Copyright IBM Corp. 2008 + * + * 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 would 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. + */ + +#include <linux/kernel.h> +#include <linux/kprobes.h> +#include <linux/random.h> + +#define div_factor 3 + +static u32 rand1, preh_val, posth_val, jph_val; +static int errors, handler_errors, num_tests; + +static noinline u32 kprobe_target(u32 value) +{ +	/* +	 * gcc ignores noinline on some architectures unless we stuff +	 * sufficient lard into the function. The get_kprobe() here is +	 * just for that. +	 * +	 * NOTE: We aren't concerned about the correctness of get_kprobe() +	 * here; hence, this call is neither under !preempt nor with the +	 * kprobe_mutex held. This is fine(tm) +	 */ +	if (get_kprobe((void *)0xdeadbeef)) +		printk(KERN_INFO "Kprobe smoke test: probe on 0xdeadbeef!\n"); + +	return (value / div_factor); +} + +static int kp_pre_handler(struct kprobe *p, struct pt_regs *regs) +{ +	preh_val = (rand1 / div_factor); +	return 0; +} + +static void kp_post_handler(struct kprobe *p, struct pt_regs *regs, +		unsigned long flags) +{ +	if (preh_val != (rand1 / div_factor)) { +		handler_errors++; +		printk(KERN_ERR "Kprobe smoke test failed: " +				"incorrect value in post_handler\n"); +	} +	posth_val = preh_val + div_factor; +} + +static struct kprobe kp = { +	.symbol_name = "kprobe_target", +	.pre_handler = kp_pre_handler, +	.post_handler = kp_post_handler +}; + +static int test_kprobe(void) +{ +	int ret; + +	ret = register_kprobe(&kp); +	if (ret < 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"register_kprobe returned %d\n", ret); +		return ret; +	} + +	ret = kprobe_target(rand1); +	unregister_kprobe(&kp); + +	if (preh_val == 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"kprobe pre_handler not called\n"); +		handler_errors++; +	} + +	if (posth_val == 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"kprobe post_handler not called\n"); +		handler_errors++; +	} + +	return 0; +} + +static u32 j_kprobe_target(u32 value) +{ +	if (value != rand1) { +		handler_errors++; +		printk(KERN_ERR "Kprobe smoke test failed: " +				"incorrect value in jprobe handler\n"); +	} + +	jph_val = rand1; +	jprobe_return(); +	return 0; +} + +static struct jprobe jp = { +	.entry		= j_kprobe_target, +	.kp.symbol_name = "kprobe_target" +}; + +static int test_jprobe(void) +{ +	int ret; + +	ret = register_jprobe(&jp); +	if (ret < 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"register_jprobe returned %d\n", ret); +		return ret; +	} + +	ret = kprobe_target(rand1); +	unregister_jprobe(&jp); +	if (jph_val == 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"jprobe handler not called\n"); +		handler_errors++; +	} + +	return 0; +} + +#ifdef CONFIG_KRETPROBES +static u32 krph_val; + +static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) +{ +	unsigned long ret = regs_return_value(regs); + +	if (ret != (rand1 / div_factor)) { +		handler_errors++; +		printk(KERN_ERR "Kprobe smoke test failed: " +				"incorrect value in kretprobe handler\n"); +	} + +	krph_val = (rand1 / div_factor); +	return 0; +} + +static struct kretprobe rp = { +	.handler	= return_handler, +	.kp.symbol_name = "kprobe_target" +}; + +static int test_kretprobe(void) +{ +	int ret; + +	ret = register_kretprobe(&rp); +	if (ret < 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"register_kretprobe returned %d\n", ret); +		return ret; +	} + +	ret = kprobe_target(rand1); +	unregister_kretprobe(&rp); +	if (krph_val == 0) { +		printk(KERN_ERR "Kprobe smoke test failed: " +				"kretprobe handler not called\n"); +		handler_errors++; +	} + +	return 0; +} +#endif /* CONFIG_KRETPROBES */ + +int init_test_probes(void) +{ +	int ret; + +	do { +		rand1 = random32(); +	} while (rand1 <= div_factor); + +	printk(KERN_INFO "Kprobe smoke test started\n"); +	num_tests++; +	ret = test_kprobe(); +	if (ret < 0) +		errors++; + +	num_tests++; +	ret = test_jprobe(); +	if (ret < 0) +		errors++; + +#ifdef CONFIG_KRETPROBES +	num_tests++; +	ret = test_kretprobe(); +	if (ret < 0) +		errors++; +#endif /* CONFIG_KRETPROBES */ + +	if (errors) +		printk(KERN_ERR "BUG: Kprobe smoke test: %d out of " +				"%d tests failed\n", errors, num_tests); +	else if (handler_errors) +		printk(KERN_ERR "BUG: Kprobe smoke test: %d error(s) " +				"running handlers\n", handler_errors); +	else +		printk(KERN_INFO "Kprobe smoke test passed successfully\n"); + +	return 0; +} diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index c4ecb2994ba3..f535b9b5eb00 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -494,6 +494,18 @@ config RCU_TORTURE_TEST  	  Say M if you want the RCU torture tests to build as a module.  	  Say N if you are unsure. +config KPROBES_SANITY_TEST +	bool "Kprobes sanity tests" +	depends on DEBUG_KERNEL +	depends on KPROBES +	default n +	help +	  This option provides for testing basic kprobes functionality on +	  boot. A sample kprobe, jprobe and kretprobe are inserted and +	  verified for functionality. + +	  Say N if you are unsure. +  config LKDTM  	tristate "Linux Kernel Dump Test Tool Module"  	depends on DEBUG_KERNEL | 
