diff options
author | Ananth N Mavinakayanahalli <ananth@in.ibm.com> | 2008-01-30 07:32:53 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-01-30 07:32:53 -0500 |
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, | |||
182 | } | 182 | } |
183 | } | 183 | } |
184 | 184 | ||
185 | #ifdef CONFIG_KPROBES_SANITY_TEST | ||
186 | extern int init_test_probes(void); | ||
187 | #else | ||
188 | static inline int init_test_probes(void) | ||
189 | { | ||
190 | return 0; | ||
191 | } | ||
192 | #endif /* CONFIG_KPROBES_SANITY_TEST */ | ||
193 | |||
185 | extern spinlock_t kretprobe_lock; | 194 | extern spinlock_t kretprobe_lock; |
186 | extern struct mutex kprobe_mutex; | 195 | extern struct mutex kprobe_mutex; |
187 | extern int arch_prepare_kprobe(struct kprobe *p); | 196 | extern int arch_prepare_kprobe(struct kprobe *p); |
@@ -227,6 +236,7 @@ void unregister_kretprobe(struct kretprobe *rp); | |||
227 | 236 | ||
228 | void kprobe_flush_task(struct task_struct *tk); | 237 | void kprobe_flush_task(struct task_struct *tk); |
229 | void recycle_rp_inst(struct kretprobe_instance *ri, struct hlist_head *head); | 238 | void recycle_rp_inst(struct kretprobe_instance *ri, struct hlist_head *head); |
239 | |||
230 | #else /* CONFIG_KPROBES */ | 240 | #else /* CONFIG_KPROBES */ |
231 | 241 | ||
232 | #define __kprobes /**/ | 242 | #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 | |||
43 | obj-$(CONFIG_CGROUP_NS) += ns_cgroup.o | 43 | obj-$(CONFIG_CGROUP_NS) += ns_cgroup.o |
44 | obj-$(CONFIG_IKCONFIG) += configs.o | 44 | obj-$(CONFIG_IKCONFIG) += configs.o |
45 | obj-$(CONFIG_STOP_MACHINE) += stop_machine.o | 45 | obj-$(CONFIG_STOP_MACHINE) += stop_machine.o |
46 | obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o | ||
46 | obj-$(CONFIG_AUDIT) += audit.o auditfilter.o | 47 | obj-$(CONFIG_AUDIT) += audit.o auditfilter.o |
47 | obj-$(CONFIG_AUDITSYSCALL) += auditsc.o | 48 | obj-$(CONFIG_AUDITSYSCALL) += auditsc.o |
48 | obj-$(CONFIG_AUDIT_TREE) += audit_tree.o | 49 | 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) | |||
824 | if (!err) | 824 | if (!err) |
825 | err = register_die_notifier(&kprobe_exceptions_nb); | 825 | err = register_die_notifier(&kprobe_exceptions_nb); |
826 | 826 | ||
827 | if (!err) | ||
828 | init_test_probes(); | ||
827 | return err; | 829 | return err; |
828 | } | 830 | } |
829 | 831 | ||
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 @@ | |||
1 | /* | ||
2 | * test_kprobes.c - simple sanity test for *probes | ||
3 | * | ||
4 | * Copyright IBM Corp. 2008 | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it would be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See | ||
14 | * the GNU General Public License for more details. | ||
15 | */ | ||
16 | |||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/kprobes.h> | ||
19 | #include <linux/random.h> | ||
20 | |||
21 | #define div_factor 3 | ||
22 | |||
23 | static u32 rand1, preh_val, posth_val, jph_val; | ||
24 | static int errors, handler_errors, num_tests; | ||
25 | |||
26 | static noinline u32 kprobe_target(u32 value) | ||
27 | { | ||
28 | /* | ||
29 | * gcc ignores noinline on some architectures unless we stuff | ||
30 | * sufficient lard into the function. The get_kprobe() here is | ||
31 | * just for that. | ||
32 | * | ||
33 | * NOTE: We aren't concerned about the correctness of get_kprobe() | ||
34 | * here; hence, this call is neither under !preempt nor with the | ||
35 | * kprobe_mutex held. This is fine(tm) | ||
36 | */ | ||
37 | if (get_kprobe((void *)0xdeadbeef)) | ||
38 | printk(KERN_INFO "Kprobe smoke test: probe on 0xdeadbeef!\n"); | ||
39 | |||
40 | return (value / div_factor); | ||
41 | } | ||
42 | |||
43 | static int kp_pre_handler(struct kprobe *p, struct pt_regs *regs) | ||
44 | { | ||
45 | preh_val = (rand1 / div_factor); | ||
46 | return 0; | ||
47 | } | ||
48 | |||
49 | static void kp_post_handler(struct kprobe *p, struct pt_regs *regs, | ||
50 | unsigned long flags) | ||
51 | { | ||
52 | if (preh_val != (rand1 / div_factor)) { | ||
53 | handler_errors++; | ||
54 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
55 | "incorrect value in post_handler\n"); | ||
56 | } | ||
57 | posth_val = preh_val + div_factor; | ||
58 | } | ||
59 | |||
60 | static struct kprobe kp = { | ||
61 | .symbol_name = "kprobe_target", | ||
62 | .pre_handler = kp_pre_handler, | ||
63 | .post_handler = kp_post_handler | ||
64 | }; | ||
65 | |||
66 | static int test_kprobe(void) | ||
67 | { | ||
68 | int ret; | ||
69 | |||
70 | ret = register_kprobe(&kp); | ||
71 | if (ret < 0) { | ||
72 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
73 | "register_kprobe returned %d\n", ret); | ||
74 | return ret; | ||
75 | } | ||
76 | |||
77 | ret = kprobe_target(rand1); | ||
78 | unregister_kprobe(&kp); | ||
79 | |||
80 | if (preh_val == 0) { | ||
81 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
82 | "kprobe pre_handler not called\n"); | ||
83 | handler_errors++; | ||
84 | } | ||
85 | |||
86 | if (posth_val == 0) { | ||
87 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
88 | "kprobe post_handler not called\n"); | ||
89 | handler_errors++; | ||
90 | } | ||
91 | |||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | static u32 j_kprobe_target(u32 value) | ||
96 | { | ||
97 | if (value != rand1) { | ||
98 | handler_errors++; | ||
99 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
100 | "incorrect value in jprobe handler\n"); | ||
101 | } | ||
102 | |||
103 | jph_val = rand1; | ||
104 | jprobe_return(); | ||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static struct jprobe jp = { | ||
109 | .entry = j_kprobe_target, | ||
110 | .kp.symbol_name = "kprobe_target" | ||
111 | }; | ||
112 | |||
113 | static int test_jprobe(void) | ||
114 | { | ||
115 | int ret; | ||
116 | |||
117 | ret = register_jprobe(&jp); | ||
118 | if (ret < 0) { | ||
119 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
120 | "register_jprobe returned %d\n", ret); | ||
121 | return ret; | ||
122 | } | ||
123 | |||
124 | ret = kprobe_target(rand1); | ||
125 | unregister_jprobe(&jp); | ||
126 | if (jph_val == 0) { | ||
127 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
128 | "jprobe handler not called\n"); | ||
129 | handler_errors++; | ||
130 | } | ||
131 | |||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | #ifdef CONFIG_KRETPROBES | ||
136 | static u32 krph_val; | ||
137 | |||
138 | static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) | ||
139 | { | ||
140 | unsigned long ret = regs_return_value(regs); | ||
141 | |||
142 | if (ret != (rand1 / div_factor)) { | ||
143 | handler_errors++; | ||
144 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
145 | "incorrect value in kretprobe handler\n"); | ||
146 | } | ||
147 | |||
148 | krph_val = (rand1 / div_factor); | ||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static struct kretprobe rp = { | ||
153 | .handler = return_handler, | ||
154 | .kp.symbol_name = "kprobe_target" | ||
155 | }; | ||
156 | |||
157 | static int test_kretprobe(void) | ||
158 | { | ||
159 | int ret; | ||
160 | |||
161 | ret = register_kretprobe(&rp); | ||
162 | if (ret < 0) { | ||
163 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
164 | "register_kretprobe returned %d\n", ret); | ||
165 | return ret; | ||
166 | } | ||
167 | |||
168 | ret = kprobe_target(rand1); | ||
169 | unregister_kretprobe(&rp); | ||
170 | if (krph_val == 0) { | ||
171 | printk(KERN_ERR "Kprobe smoke test failed: " | ||
172 | "kretprobe handler not called\n"); | ||
173 | handler_errors++; | ||
174 | } | ||
175 | |||
176 | return 0; | ||
177 | } | ||
178 | #endif /* CONFIG_KRETPROBES */ | ||
179 | |||
180 | int init_test_probes(void) | ||
181 | { | ||
182 | int ret; | ||
183 | |||
184 | do { | ||
185 | rand1 = random32(); | ||
186 | } while (rand1 <= div_factor); | ||
187 | |||
188 | printk(KERN_INFO "Kprobe smoke test started\n"); | ||
189 | num_tests++; | ||
190 | ret = test_kprobe(); | ||
191 | if (ret < 0) | ||
192 | errors++; | ||
193 | |||
194 | num_tests++; | ||
195 | ret = test_jprobe(); | ||
196 | if (ret < 0) | ||
197 | errors++; | ||
198 | |||
199 | #ifdef CONFIG_KRETPROBES | ||
200 | num_tests++; | ||
201 | ret = test_kretprobe(); | ||
202 | if (ret < 0) | ||
203 | errors++; | ||
204 | #endif /* CONFIG_KRETPROBES */ | ||
205 | |||
206 | if (errors) | ||
207 | printk(KERN_ERR "BUG: Kprobe smoke test: %d out of " | ||
208 | "%d tests failed\n", errors, num_tests); | ||
209 | else if (handler_errors) | ||
210 | printk(KERN_ERR "BUG: Kprobe smoke test: %d error(s) " | ||
211 | "running handlers\n", handler_errors); | ||
212 | else | ||
213 | printk(KERN_INFO "Kprobe smoke test passed successfully\n"); | ||
214 | |||
215 | return 0; | ||
216 | } | ||
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 | |||
494 | Say M if you want the RCU torture tests to build as a module. | 494 | Say M if you want the RCU torture tests to build as a module. |
495 | Say N if you are unsure. | 495 | Say N if you are unsure. |
496 | 496 | ||
497 | config KPROBES_SANITY_TEST | ||
498 | bool "Kprobes sanity tests" | ||
499 | depends on DEBUG_KERNEL | ||
500 | depends on KPROBES | ||
501 | default n | ||
502 | help | ||
503 | This option provides for testing basic kprobes functionality on | ||
504 | boot. A sample kprobe, jprobe and kretprobe are inserted and | ||
505 | verified for functionality. | ||
506 | |||
507 | Say N if you are unsure. | ||
508 | |||
497 | config LKDTM | 509 | config LKDTM |
498 | tristate "Linux Kernel Dump Test Tool Module" | 510 | tristate "Linux Kernel Dump Test Tool Module" |
499 | depends on DEBUG_KERNEL | 511 | depends on DEBUG_KERNEL |