ARM: kprobes: Add basic API tests
authorJon Medhurst <tixy@yxit.co.uk>
Sun, 28 Aug 2011 15:02:38 +0000 (16:02 +0100)
committerJon Medhurst <tixy@yxit.co.uk>
Tue, 20 Sep 2011 18:17:43 +0000 (18:17 +0000)
These test that the different kinds of probes can be successfully placed
into ARM and Thumb code and that the handlers are called correctly when
this code is executed.

Signed-off-by: Jon Medhurst <tixy@yxit.co.uk>
Acked-by: Nicolas Pitre <nicolas.pitre@linaro.org>
arch/arm/kernel/Makefile
arch/arm/kernel/kprobes-test.c [new file with mode: 0644]

index f7887dc..c7cbd96 100644 (file)
@@ -43,6 +43,8 @@ obj-$(CONFIG_KPROBES)         += kprobes-thumb.o
 else
 obj-$(CONFIG_KPROBES)          += kprobes-arm.o
 endif
+obj-$(CONFIG_ARM_KPROBES_TEST) += test-kprobes.o
+test-kprobes-objs              := kprobes-test.o
 obj-$(CONFIG_ATAGS_PROC)       += atags.o
 obj-$(CONFIG_OABI_COMPAT)      += sys_oabi-compat.o
 obj-$(CONFIG_ARM_THUMBEE)      += thumbee.o
diff --git a/arch/arm/kernel/kprobes-test.c b/arch/arm/kernel/kprobes-test.c
new file mode 100644 (file)
index 0000000..9fff044
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * arch/arm/kernel/kprobes-test.c
+ *
+ * Copyright (C) 2011 Jon Medhurst <tixy@yxit.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kprobes.h>
+
+#include "kprobes.h"
+
+
+/*
+ * Test basic API
+ */
+
+static bool test_regs_ok;
+static int test_func_instance;
+static int pre_handler_called;
+static int post_handler_called;
+static int jprobe_func_called;
+static int kretprobe_handler_called;
+
+#define FUNC_ARG1 0x12345678
+#define FUNC_ARG2 0xabcdef
+
+
+#ifndef CONFIG_THUMB2_KERNEL
+
+long arm_func(long r0, long r1);
+
+static void __used __naked __arm_kprobes_test_func(void)
+{
+       __asm__ __volatile__ (
+               ".arm                                   \n\t"
+               ".type arm_func, %%function             \n\t"
+               "arm_func:                              \n\t"
+               "adds   r0, r0, r1                      \n\t"
+               "bx     lr                              \n\t"
+               ".code "NORMAL_ISA       /* Back to Thumb if necessary */
+               : : : "r0", "r1", "cc"
+       );
+}
+
+#else /* CONFIG_THUMB2_KERNEL */
+
+long thumb16_func(long r0, long r1);
+long thumb32even_func(long r0, long r1);
+long thumb32odd_func(long r0, long r1);
+
+static void __used __naked __thumb_kprobes_test_funcs(void)
+{
+       __asm__ __volatile__ (
+               ".type thumb16_func, %%function         \n\t"
+               "thumb16_func:                          \n\t"
+               "adds.n r0, r0, r1                      \n\t"
+               "bx     lr                              \n\t"
+
+               ".align                                 \n\t"
+               ".type thumb32even_func, %%function     \n\t"
+               "thumb32even_func:                      \n\t"
+               "adds.w r0, r0, r1                      \n\t"
+               "bx     lr                              \n\t"
+
+               ".align                                 \n\t"
+               "nop.n                                  \n\t"
+               ".type thumb32odd_func, %%function      \n\t"
+               "thumb32odd_func:                       \n\t"
+               "adds.w r0, r0, r1                      \n\t"
+               "bx     lr                              \n\t"
+
+               : : : "r0", "r1", "cc"
+       );
+}
+
+#endif /* CONFIG_THUMB2_KERNEL */
+
+
+static int call_test_func(long (*func)(long, long), bool check_test_regs)
+{
+       long ret;
+
+       ++test_func_instance;
+       test_regs_ok = false;
+
+       ret = (*func)(FUNC_ARG1, FUNC_ARG2);
+       if (ret != FUNC_ARG1 + FUNC_ARG2) {
+               pr_err("FAIL: call_test_func: func returned %lx\n", ret);
+               return false;
+       }
+
+       if (check_test_regs && !test_regs_ok) {
+               pr_err("FAIL: test regs not OK\n");
+               return false;
+       }
+
+       return true;
+}
+
+static int __kprobes pre_handler(struct kprobe *p, struct pt_regs *regs)
+{
+       pre_handler_called = test_func_instance;
+       if (regs->ARM_r0 == FUNC_ARG1 && regs->ARM_r1 == FUNC_ARG2)
+               test_regs_ok = true;
+       return 0;
+}
+
+static void __kprobes post_handler(struct kprobe *p, struct pt_regs *regs,
+                               unsigned long flags)
+{
+       post_handler_called = test_func_instance;
+       if (regs->ARM_r0 != FUNC_ARG1 + FUNC_ARG2 || regs->ARM_r1 != FUNC_ARG2)
+               test_regs_ok = false;
+}
+
+static struct kprobe the_kprobe = {
+       .addr           = 0,
+       .pre_handler    = pre_handler,
+       .post_handler   = post_handler
+};
+
+static int test_kprobe(long (*func)(long, long))
+{
+       int ret;
+
+       the_kprobe.addr = (kprobe_opcode_t *)func;
+       ret = register_kprobe(&the_kprobe);
+       if (ret < 0) {
+               pr_err("FAIL: register_kprobe failed with %d\n", ret);
+               return ret;
+       }
+
+       ret = call_test_func(func, true);
+
+       unregister_kprobe(&the_kprobe);
+       the_kprobe.flags = 0; /* Clear disable flag to allow reuse */
+
+       if (!ret)
+               return -EINVAL;
+       if (pre_handler_called != test_func_instance) {
+               pr_err("FAIL: kprobe pre_handler not called\n");
+               return -EINVAL;
+       }
+       if (post_handler_called != test_func_instance) {
+               pr_err("FAIL: kprobe post_handler not called\n");
+               return -EINVAL;
+       }
+       if (!call_test_func(func, false))
+               return -EINVAL;
+       if (pre_handler_called == test_func_instance ||
+                               post_handler_called == test_func_instance) {
+               pr_err("FAIL: probe called after unregistering\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void __kprobes jprobe_func(long r0, long r1)
+{
+       jprobe_func_called = test_func_instance;
+       if (r0 == FUNC_ARG1 && r1 == FUNC_ARG2)
+               test_regs_ok = true;
+       jprobe_return();
+}
+
+static struct jprobe the_jprobe = {
+       .entry          = jprobe_func,
+};
+
+static int test_jprobe(long (*func)(long, long))
+{
+       int ret;
+
+       the_jprobe.kp.addr = (kprobe_opcode_t *)func;
+       ret = register_jprobe(&the_jprobe);
+       if (ret < 0) {
+               pr_err("FAIL: register_jprobe failed with %d\n", ret);
+               return ret;
+       }
+
+       ret = call_test_func(func, true);
+
+       unregister_jprobe(&the_jprobe);
+       the_jprobe.kp.flags = 0; /* Clear disable flag to allow reuse */
+
+       if (!ret)
+               return -EINVAL;
+       if (jprobe_func_called != test_func_instance) {
+               pr_err("FAIL: jprobe handler function not called\n");
+               return -EINVAL;
+       }
+       if (!call_test_func(func, false))
+               return -EINVAL;
+       if (jprobe_func_called == test_func_instance) {
+               pr_err("FAIL: probe called after unregistering\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int __kprobes
+kretprobe_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
+{
+       kretprobe_handler_called = test_func_instance;
+       if (regs_return_value(regs) == FUNC_ARG1 + FUNC_ARG2)
+               test_regs_ok = true;
+       return 0;
+}
+
+static struct kretprobe the_kretprobe = {
+       .handler        = kretprobe_handler,
+};
+
+static int test_kretprobe(long (*func)(long, long))
+{
+       int ret;
+
+       the_kretprobe.kp.addr = (kprobe_opcode_t *)func;
+       ret = register_kretprobe(&the_kretprobe);
+       if (ret < 0) {
+               pr_err("FAIL: register_kretprobe failed with %d\n", ret);
+               return ret;
+       }
+
+       ret = call_test_func(func, true);
+
+       unregister_kretprobe(&the_kretprobe);
+       the_kretprobe.kp.flags = 0; /* Clear disable flag to allow reuse */
+
+       if (!ret)
+               return -EINVAL;
+       if (kretprobe_handler_called != test_func_instance) {
+               pr_err("FAIL: kretprobe handler not called\n");
+               return -EINVAL;
+       }
+       if (!call_test_func(func, false))
+               return -EINVAL;
+       if (jprobe_func_called == test_func_instance) {
+               pr_err("FAIL: kretprobe called after unregistering\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int run_api_tests(long (*func)(long, long))
+{
+       int ret;
+
+       pr_info("    kprobe\n");
+       ret = test_kprobe(func);
+       if (ret < 0)
+               return ret;
+
+       pr_info("    jprobe\n");
+       ret = test_jprobe(func);
+       if (ret < 0)
+               return ret;
+
+       pr_info("    kretprobe\n");
+       ret = test_kretprobe(func);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+
+/*
+ * Top level test functions
+ */
+
+static int __init run_all_tests(void)
+{
+       int ret = 0;
+
+       pr_info("Begining kprobe tests...\n");
+
+#ifndef CONFIG_THUMB2_KERNEL
+
+       pr_info("Probe ARM code\n");
+       ret = run_api_tests(arm_func);
+       if (ret)
+               goto out;
+
+#else /* CONFIG_THUMB2_KERNEL */
+
+       pr_info("Probe 16-bit Thumb code\n");
+       ret = run_api_tests(thumb16_func);
+       if (ret)
+               goto out;
+
+       pr_info("Probe 32-bit Thumb code, even halfword\n");
+       ret = run_api_tests(thumb32even_func);
+       if (ret)
+               goto out;
+
+       pr_info("Probe 32-bit Thumb code, odd halfword\n");
+       ret = run_api_tests(thumb32odd_func);
+       if (ret)
+               goto out;
+
+#endif
+
+out:
+       if (ret == 0)
+               pr_info("Finished kprobe tests OK\n");
+       else
+               pr_err("kprobe tests failed\n");
+
+       return ret;
+}
+
+
+/*
+ * Module setup
+ */
+
+#ifdef MODULE
+
+static void __exit kprobe_test_exit(void)
+{
+}
+
+module_init(run_all_tests)
+module_exit(kprobe_test_exit)
+MODULE_LICENSE("GPL");
+
+#else /* !MODULE */
+
+late_initcall(run_all_tests);
+
+#endif