s390/test_unwind: add kretprobe tests
authorTobias Huschle <huschle@linux.ibm.com>
Thu, 24 Feb 2022 15:14:21 +0000 (16:14 +0100)
committerVasily Gorbik <gor@linux.ibm.com>
Thu, 10 Mar 2022 14:58:17 +0000 (15:58 +0100)
Add tests to verify that s390 kretprobes maintain a correct stack
chain and ensure their proper function.

Reviewed-by: Vasily Gorbik <gor@linux.ibm.com>
Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
arch/s390/lib/test_unwind.c

index 07ef89e..c01f028 100644 (file)
@@ -132,6 +132,7 @@ static struct unwindme *unwindme;
 #define UWM_PGM                        0x40    /* Unwind from program check handler */
 #define UWM_KPROBE_ON_FTRACE   0x80    /* Unwind from kprobe handler called via ftrace. */
 #define UWM_FTRACE             0x100   /* Unwind from ftrace handler. */
+#define UWM_KRETPROBE          0x200   /* Unwind kretprobe handlers. */
 
 static __always_inline unsigned long get_psw_addr(void)
 {
@@ -143,6 +144,55 @@ static __always_inline unsigned long get_psw_addr(void)
        return psw_addr;
 }
 
+static int kretprobe_ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
+{
+       struct unwindme *u = unwindme;
+
+       u->ret = test_unwind(NULL, (u->flags & UWM_REGS) ? regs : NULL,
+                            (u->flags & UWM_SP) ? u->sp : 0);
+
+       return 0;
+}
+
+static noinline notrace void test_unwind_kretprobed_func(void)
+{
+       asm volatile("  nop\n");
+}
+
+static noinline void test_unwind_kretprobed_func_caller(void)
+{
+       test_unwind_kretprobed_func();
+}
+
+static int test_unwind_kretprobe(struct unwindme *u)
+{
+       int ret;
+       struct kretprobe my_kretprobe;
+
+       if (!IS_ENABLED(CONFIG_KPROBES))
+               kunit_skip(current_test, "requires CONFIG_KPROBES");
+
+       u->ret = -1; /* make sure kprobe is called */
+       unwindme = u;
+
+       memset(&my_kretprobe, 0, sizeof(my_kretprobe));
+       my_kretprobe.handler = kretprobe_ret_handler;
+       my_kretprobe.maxactive = 1;
+       my_kretprobe.kp.addr = (kprobe_opcode_t *)test_unwind_kretprobed_func;
+
+       ret = register_kretprobe(&my_kretprobe);
+
+       if (ret < 0) {
+               kunit_err(current_test, "register_kretprobe failed %d\n", ret);
+               return -EINVAL;
+       }
+
+       test_unwind_kretprobed_func_caller();
+       unregister_kretprobe(&my_kretprobe);
+       unwindme = NULL;
+       return u->ret;
+}
+
 static int kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
 {
        struct unwindme *u = unwindme;
@@ -254,6 +304,8 @@ static noinline int unwindme_func4(struct unwindme *u)
                return 0;
        } else if (u->flags & (UWM_PGM | UWM_KPROBE_ON_FTRACE)) {
                return test_unwind_kprobe(u);
+       } else if (u->flags & (UWM_KRETPROBE)) {
+               return test_unwind_kretprobe(u);
        } else if (u->flags & UWM_FTRACE) {
                return test_unwind_ftrace(u);
        } else {
@@ -396,6 +448,10 @@ static const struct test_params param_list[] = {
        TEST_WITH_FLAGS(UWM_FTRACE | UWM_SP),
        TEST_WITH_FLAGS(UWM_FTRACE | UWM_REGS),
        TEST_WITH_FLAGS(UWM_FTRACE | UWM_SP | UWM_REGS),
+       TEST_WITH_FLAGS(UWM_KRETPROBE),
+       TEST_WITH_FLAGS(UWM_KRETPROBE | UWM_SP),
+       TEST_WITH_FLAGS(UWM_KRETPROBE | UWM_REGS),
+       TEST_WITH_FLAGS(UWM_KRETPROBE | UWM_SP | UWM_REGS),
 };
 
 /*