]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
s390/unwind: add a test for the internal API
authorIlya Leoshkevich <iii@linux.ibm.com>
Thu, 17 Oct 2019 13:09:08 +0000 (15:09 +0200)
committerVasily Gorbik <gor@linux.ibm.com>
Sat, 30 Nov 2019 09:52:46 +0000 (10:52 +0100)
unwind_for_each_frame can take at least 8 different sets of parameters.
Add a test to make sure they all are handled in a sane way.

Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
Co-developed-by: Vasily Gorbik <gor@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
arch/s390/Kconfig
arch/s390/lib/Makefile
arch/s390/lib/test_unwind.c [new file with mode: 0644]

index f0df9e48e6515c3146848d9fe8f0059f9f171962..2528eb9d01fbc34ca185167bad2b04f4bcdc9c7d 100644 (file)
@@ -1018,3 +1018,17 @@ config S390_GUEST
          the KVM hypervisor.
 
 endmenu
+
+menu "Selftests"
+
+config S390_UNWIND_SELFTEST
+       def_tristate n
+       prompt "Test unwind functions"
+       help
+         This option enables s390 specific stack unwinder testing kernel
+         module. This option is not useful for distributions or general
+         kernels, but only for kernel developers working on architecture code.
+
+         Say N if you are unsure.
+
+endmenu
index d7c218e8b559b1ce4af8bd3ed6ccd21a328babad..28fd66d558ffbc1d703296e3652c24475c46e51f 100644 (file)
@@ -11,3 +11,6 @@ lib-$(CONFIG_UPROBES) += probes.o
 # Instrumenting memory accesses to __user data (in different address space)
 # produce false positives
 KASAN_SANITIZE_uaccess.o := n
+
+obj-$(CONFIG_S390_UNWIND_SELFTEST) += test_unwind.o
+CFLAGS_test_unwind.o += -fno-optimize-sibling-calls
diff --git a/arch/s390/lib/test_unwind.c b/arch/s390/lib/test_unwind.c
new file mode 100644 (file)
index 0000000..5636da9
--- /dev/null
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test module for unwind_for_each_frame
+ */
+
+#define pr_fmt(fmt) "test_unwind: " fmt
+#include <asm/unwind.h>
+#include <linux/completion.h>
+#include <linux/kallsyms.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/wait.h>
+
+#define BT_BUF_SIZE (PAGE_SIZE * 4)
+
+/*
+ * To avoid printk line limit split backtrace by lines
+ */
+static void print_backtrace(char *bt)
+{
+       char *p;
+
+       while (true) {
+               p = strsep(&bt, "\n");
+               if (!p)
+                       break;
+               pr_err("%s\n", p);
+       }
+}
+
+/*
+ * Calls unwind_for_each_frame(task, regs, sp) and verifies that the result
+ * contains unwindme_func2 followed by unwindme_func1.
+ */
+static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs,
+                               unsigned long sp)
+{
+       int frame_count, prev_is_func2, seen_func2_func1;
+       const int max_frames = 128;
+       struct unwind_state state;
+       size_t bt_pos = 0;
+       int ret = 0;
+       char *bt;
+
+       bt = kmalloc(BT_BUF_SIZE, GFP_KERNEL);
+       if (!bt) {
+               pr_err("failed to allocate backtrace buffer\n");
+               return -ENOMEM;
+       }
+       /* Unwind. */
+       frame_count = 0;
+       prev_is_func2 = 0;
+       seen_func2_func1 = 0;
+       unwind_for_each_frame(&state, task, regs, sp) {
+               unsigned long addr = unwind_get_return_address(&state);
+               char sym[KSYM_SYMBOL_LEN];
+
+               if (!addr || frame_count == max_frames)
+                       break;
+               sprint_symbol(sym, addr);
+               if (bt_pos < BT_BUF_SIZE) {
+                       bt_pos += snprintf(bt + bt_pos, BT_BUF_SIZE - bt_pos, "%s\n", sym);
+                       if (bt_pos >= BT_BUF_SIZE)
+                               pr_err("backtrace buffer is too small\n");
+               }
+               frame_count += 1;
+               if (prev_is_func2 && str_has_prefix(sym, "unwindme_func1"))
+                       seen_func2_func1 = 1;
+               prev_is_func2 = str_has_prefix(sym, "unwindme_func2");
+       }
+
+       /* Check the results. */
+       if (!seen_func2_func1) {
+               pr_err("unwindme_func2 and unwindme_func1 not found\n");
+               ret = -EINVAL;
+       }
+       if (frame_count == max_frames) {
+               pr_err("Maximum number of frames exceeded\n");
+               ret = -EINVAL;
+       }
+       if (ret)
+               print_backtrace(bt);
+       kfree(bt);
+       return ret;
+}
+
+/* State of the task being unwound. */
+struct unwindme {
+       int flags;
+       struct completion task_ready;
+       wait_queue_head_t task_wq;
+       unsigned long sp;
+};
+
+/* Values of unwindme.flags. */
+#define UWM_DEFAULT    0x0
+#define UWM_THREAD     0x1     /* Unwind a separate task. */
+#define UWM_REGS       0x2     /* Pass regs to test_unwind(). */
+#define UWM_SP         0x4     /* Pass sp to test_unwind(). */
+#define UWM_CALLER     0x8     /* Unwind starting from caller. */
+
+static __always_inline unsigned long get_psw_addr(void)
+{
+       unsigned long psw_addr;
+
+       asm volatile(
+               "basr   %[psw_addr],0\n"
+               : [psw_addr] "=d" (psw_addr));
+       return psw_addr;
+}
+
+/* This function may or may not appear in the backtrace. */
+static noinline int unwindme_func4(struct unwindme *u)
+{
+       if (!(u->flags & UWM_CALLER))
+               u->sp = current_frame_address();
+       if (u->flags & UWM_THREAD) {
+               complete(&u->task_ready);
+               wait_event(u->task_wq, kthread_should_park());
+               kthread_parkme();
+               return 0;
+       } else {
+               struct pt_regs regs;
+
+               memset(&regs, 0, sizeof(regs));
+               regs.psw.addr = get_psw_addr();
+               regs.gprs[15] = current_stack_pointer();
+               return test_unwind(NULL,
+                                  (u->flags & UWM_REGS) ? &regs : NULL,
+                                  (u->flags & UWM_SP) ? u->sp : 0);
+       }
+}
+
+/* This function may or may not appear in the backtrace. */
+static noinline int unwindme_func3(struct unwindme *u)
+{
+       u->sp = current_frame_address();
+       return unwindme_func4(u);
+}
+
+/* This function must appear in the backtrace. */
+static noinline int unwindme_func2(struct unwindme *u)
+{
+       return unwindme_func3(u);
+}
+
+/* This function must follow unwindme_func2 in the backtrace. */
+static noinline int unwindme_func1(void *u)
+{
+       return unwindme_func2((struct unwindme *)u);
+}
+
+/* Spawns a task and passes it to test_unwind(). */
+static int test_unwind_task(struct unwindme *u)
+{
+       struct task_struct *task;
+       int ret;
+
+       /* Initialize thread-related fields. */
+       init_completion(&u->task_ready);
+       init_waitqueue_head(&u->task_wq);
+
+       /*
+        * Start the task and wait until it reaches unwindme_func4() and sleeps
+        * in (task_ready, unwind_done] range.
+        */
+       task = kthread_run(unwindme_func1, u, "%s", __func__);
+       if (IS_ERR(task)) {
+               pr_err("kthread_run() failed\n");
+               return PTR_ERR(task);
+       }
+       /*
+        * Make sure task reaches unwindme_func4 before parking it,
+        * we might park it before kthread function has been executed otherwise
+        */
+       wait_for_completion(&u->task_ready);
+       kthread_park(task);
+       /* Unwind. */
+       ret = test_unwind(task, NULL, (u->flags & UWM_SP) ? u->sp : 0);
+       kthread_stop(task);
+       return ret;
+}
+
+static int test_unwind_flags(int flags)
+{
+       struct unwindme u;
+
+       u.flags = flags;
+       if (u.flags & UWM_THREAD)
+               return test_unwind_task(&u);
+       else
+               return unwindme_func1(&u);
+}
+
+static int test_unwind_init(void)
+{
+       int ret = 0;
+
+#define TEST(flags)                                                    \
+do {                                                                   \
+       pr_info("[ RUN      ] " #flags "\n");                           \
+       if (!test_unwind_flags((flags))) {                              \
+               pr_info("[       OK ] " #flags "\n");                   \
+       } else {                                                        \
+               pr_err("[  FAILED  ] " #flags "\n");                    \
+               ret = -EINVAL;                                          \
+       }                                                               \
+} while (0)
+
+       TEST(UWM_DEFAULT);
+       TEST(UWM_SP);
+       TEST(UWM_REGS);
+       TEST(UWM_SP | UWM_REGS);
+       TEST(UWM_CALLER | UWM_SP);
+       TEST(UWM_CALLER | UWM_SP | UWM_REGS);
+       TEST(UWM_THREAD);
+       TEST(UWM_THREAD | UWM_SP);
+       TEST(UWM_THREAD | UWM_CALLER | UWM_SP);
+#undef TEST
+
+       return ret;
+}
+
+static void test_unwind_exit(void)
+{
+}
+
+module_init(test_unwind_init);
+module_exit(test_unwind_exit);
+MODULE_LICENSE("GPL");