Commit 9ba142f4 authored by Vasily Gorbik's avatar Vasily Gorbik
Browse files

s390/test_unwind: fix and extend kprobes test



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: default avatarVasily Gorbik <gor@linux.ibm.com>
parent 829ec749
Loading
Loading
Loading
Loading
+50 −33
Original line number Diff line number Diff line
@@ -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),