s390/test_unwind: fix and extend kprobes test
authorVasily Gorbik <gor@linux.ibm.com>
Wed, 2 Feb 2022 23:49:41 +0000 (00:49 +0100)
committerVasily Gorbik <gor@linux.ibm.com>
Tue, 1 Mar 2022 20:05:09 +0000 (21:05 +0100)
Running kprobe test on a kernel built with clang 14 didn't actually
trigger pgm_pre_handler() and no unwinder code was called. Even though
do_report_trap() is a global symbol, clang inlined it in several local
functions including illegal_op() handler, so that kprobbing a global
symbol didn't have a desired effect.

To achieve the same test result (unwinding from a program check
handler) introduce a local function and probe an instruction in the
middle, so that kprobe doesn't take KPROBE_ON_FTRACE path.

While at it, add another test for KPROBE_ON_FTRACE.

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

index 2224694..b209014 100644 (file)
@@ -129,8 +129,9 @@ static struct unwindme *unwindme;
 #define UWM_CALLER             0x8     /* Unwind starting from caller. */
 #define UWM_SWITCH_STACK       0x10    /* Use call_on_stack. */
 #define UWM_IRQ                        0x20    /* Unwind from irq context. */
-#define UWM_PGM                        0x40    /* Unwind from program check handler. */
-#define UWM_FTRACE             0x80    /* Unwind from ftrace handler. */
+#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. */
 
 static __always_inline unsigned long get_psw_addr(void)
 {
@@ -142,7 +143,7 @@ static __always_inline unsigned long get_psw_addr(void)
        return psw_addr;
 }
 
-static int pgm_pre_handler(struct kprobe *p, struct pt_regs *regs)
+static int kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
 {
        struct unwindme *u = unwindme;
 
@@ -151,6 +152,46 @@ static int pgm_pre_handler(struct kprobe *p, struct pt_regs *regs)
        return 0;
 }
 
+extern const char test_unwind_kprobed_insn[];
+
+static noinline void test_unwind_kprobed_func(void)
+{
+       asm volatile(
+               "       nopr    %%r7\n"
+               "test_unwind_kprobed_insn:\n"
+               "       nopr    %%r7\n"
+               :);
+}
+
+static int test_unwind_kprobe(struct unwindme *u)
+{
+       struct kprobe kp;
+       int ret;
+
+       if (!IS_ENABLED(CONFIG_KPROBES))
+               kunit_skip(current_test, "requires CONFIG_KPROBES");
+       if (!IS_ENABLED(CONFIG_KPROBES_ON_FTRACE) && u->flags & UWM_KPROBE_ON_FTRACE)
+               kunit_skip(current_test, "requires CONFIG_KPROBES_ON_FTRACE");
+
+       u->ret = -1; /* make sure kprobe is called */
+       unwindme = u;
+       memset(&kp, 0, sizeof(kp));
+       kp.pre_handler = kprobe_pre_handler;
+       kp.addr = u->flags & UWM_KPROBE_ON_FTRACE ?
+                               (kprobe_opcode_t *)test_unwind_kprobed_func :
+                               (kprobe_opcode_t *)test_unwind_kprobed_insn;
+       ret = register_kprobe(&kp);
+       if (ret < 0) {
+               kunit_err(current_test, "register_kprobe failed %d\n", ret);
+               return -EINVAL;
+       }
+
+       test_unwind_kprobed_func();
+       unregister_kprobe(&kp);
+       unwindme = NULL;
+       return u->ret;
+}
+
 static void notrace __used test_unwind_ftrace_handler(unsigned long ip,
                                                      unsigned long parent_ip,
                                                      struct ftrace_ops *fops,
@@ -212,36 +253,8 @@ static noinline int unwindme_func4(struct unwindme *u)
                wait_event(u->task_wq, kthread_should_park());
                kthread_parkme();
                return 0;
-       } else if (u->flags & UWM_PGM) {
-               struct kprobe kp;
-               int ret;
-
-               if (!IS_ENABLED(CONFIG_KPROBES))
-                       kunit_skip(current_test, "requires CONFIG_KPROBES");
-
-               unwindme = u;
-               memset(&kp, 0, sizeof(kp));
-               kp.symbol_name = "do_report_trap";
-               kp.pre_handler = pgm_pre_handler;
-               ret = register_kprobe(&kp);
-               if (ret < 0) {
-                       kunit_err(current_test, "register_kprobe failed %d\n", ret);
-                       return -EINVAL;
-               }
-
-               /*
-                * Trigger operation exception; use insn notation to bypass
-                * llvm's integrated assembler sanity checks.
-                */
-               asm volatile(
-                       "       .insn   e,0x0000\n"     /* illegal opcode */
-                       "0:     nopr    %%r7\n"
-                       EX_TABLE(0b, 0b)
-                       :);
-
-               unregister_kprobe(&kp);
-               unwindme = NULL;
-               return u->ret;
+       } else if (u->flags & (UWM_PGM | UWM_KPROBE_ON_FTRACE)) {
+               return test_unwind_kprobe(u);
        } else if (u->flags & UWM_FTRACE) {
                return test_unwind_ftrace(u);
        } else {
@@ -376,6 +389,10 @@ static const struct test_params param_list[] = {
        TEST_WITH_FLAGS(UWM_PGM | UWM_SP),
        TEST_WITH_FLAGS(UWM_PGM | UWM_REGS),
        TEST_WITH_FLAGS(UWM_PGM | UWM_SP | UWM_REGS),
+       TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE),
+       TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE | UWM_SP),
+       TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE | UWM_REGS),
+       TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE | UWM_SP | UWM_REGS),
        TEST_WITH_FLAGS(UWM_FTRACE),
        TEST_WITH_FLAGS(UWM_FTRACE | UWM_SP),
        TEST_WITH_FLAGS(UWM_FTRACE | UWM_REGS),