All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Peter Zijlstra <peterz@infradead.org>
Cc: linux-kernel@vger.kernel.org,
	Thomas Gleixner <tglx@linutronix.de>,
	"Paul E . McKenney" <paulmck@kernel.org>,
	Boqun Feng <boqun.feng@gmail.com>,
	"H . Peter Anvin" <hpa@zytor.com>, Paul Turner <pjt@google.com>,
	linux-api@vger.kernel.org,
	Christian Brauner <christian.brauner@ubuntu.com>,
	Florian Weimer <fw@deneb.enyo.de>,
	David.Laight@ACULAB.COM, carlos@redhat.com,
	Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
	Shuah Khan <shuah@kernel.org>,
	linux-kselftest@vger.kernel.org
Subject: [RFC PATCH v2 2/2] selftests: rseq: test abort-at-ip extension on x86
Date: Mon, 10 Jan 2022 12:16:11 -0500	[thread overview]
Message-ID: <20220110171611.8351-2-mathieu.desnoyers@efficios.com> (raw)
In-Reply-To: <20220110171611.8351-1-mathieu.desnoyers@efficios.com>

Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: linux-kselftest@vger.kernel.org
---
 tools/testing/selftests/rseq/.gitignore       |   1 +
 tools/testing/selftests/rseq/Makefile         |   3 +-
 tools/testing/selftests/rseq/rseq.c           |  21 ++
 tools/testing/selftests/rseq/rseq.h           |  12 +
 .../selftests/rseq/rseq_ext_abort_at_ip.c     | 285 ++++++++++++++++++
 5 files changed, 321 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/rseq/rseq_ext_abort_at_ip.c

diff --git a/tools/testing/selftests/rseq/.gitignore b/tools/testing/selftests/rseq/.gitignore
index 5910888ebfe1..472152afab77 100644
--- a/tools/testing/selftests/rseq/.gitignore
+++ b/tools/testing/selftests/rseq/.gitignore
@@ -5,3 +5,4 @@ basic_rseq_op_test
 param_test
 param_test_benchmark
 param_test_compare_twice
+rseq_ext_abort_at_ip
diff --git a/tools/testing/selftests/rseq/Makefile b/tools/testing/selftests/rseq/Makefile
index 2af9d39a9716..dae0dba7552b 100644
--- a/tools/testing/selftests/rseq/Makefile
+++ b/tools/testing/selftests/rseq/Makefile
@@ -13,7 +13,8 @@ LDLIBS += -lpthread
 OVERRIDE_TARGETS = 1
 
 TEST_GEN_PROGS = basic_test basic_percpu_ops_test param_test \
-		param_test_benchmark param_test_compare_twice
+		param_test_benchmark param_test_compare_twice \
+		rseq_ext_abort_at_ip
 
 TEST_GEN_PROGS_EXTENDED = librseq.so
 
diff --git a/tools/testing/selftests/rseq/rseq.c b/tools/testing/selftests/rseq/rseq.c
index 7159eb777fd3..3e012f581ddb 100644
--- a/tools/testing/selftests/rseq/rseq.c
+++ b/tools/testing/selftests/rseq/rseq.c
@@ -73,6 +73,27 @@ static int sys_rseq(volatile struct rseq *rseq_abi, uint32_t rseq_len,
 	return syscall(__NR_rseq, rseq_abi, rseq_len, flags, sig);
 }
 
+/*
+ * Return 0 if extension is available, negative error otherwise.
+ */
+int rseq_query_extension(enum rseq_extension ext)
+{
+	int ret;
+	unsigned int flags;
+
+	switch (ext) {
+	case RSEQ_EXT_ABORT_AT_IP:
+		flags = RSEQ_FLAG_QUERY_ABORT_AT_IP;
+		break;
+	default:
+		return -EINVAL;
+	}
+	ret = sys_rseq(NULL, 0, flags, 0);
+	if (!ret)
+		return 0;
+	return -errno;
+}
+
 int rseq_register_current_thread(void)
 {
 	int rc, ret = 0;
diff --git a/tools/testing/selftests/rseq/rseq.h b/tools/testing/selftests/rseq/rseq.h
index 3f63eb362b92..34c80402c078 100644
--- a/tools/testing/selftests/rseq/rseq.h
+++ b/tools/testing/selftests/rseq/rseq.h
@@ -67,6 +67,12 @@ extern int __rseq_handled;
 		abort();		\
 	} while (0)
 
+enum rseq_extension {
+	RSEQ_EXT_ABORT_AT_IP = 0,
+};
+
+#define ASM_RSEQ_CS_FLAG_ABORT_AT_IP	(1U << 3)
+
 #if defined(__x86_64__) || defined(__i386__)
 #include <rseq-x86.h>
 #elif defined(__ARMEL__)
@@ -162,4 +168,10 @@ static inline void rseq_prepare_unload(void)
 	rseq_clear_rseq_cs();
 }
 
+/*
+ * Query whether rseq extension is available. Return 0 if available, negative
+ * error value otherwise.
+ */
+int rseq_query_extension(enum rseq_extension ext);
+
 #endif  /* RSEQ_H_ */
diff --git a/tools/testing/selftests/rseq/rseq_ext_abort_at_ip.c b/tools/testing/selftests/rseq/rseq_ext_abort_at_ip.c
new file mode 100644
index 000000000000..4cddf7433cc3
--- /dev/null
+++ b/tools/testing/selftests/rseq/rseq_ext_abort_at_ip.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * RSEQ abort-at-ip extension test.
+ *
+ * The test test_abort_at_ip_loop() implements an infinite loop which only exits when
+ * aborted.  This rseq critical section is defined with the abort-at-ip
+ * extension, which requires the userspace abort handler to reajust the stack pointer.
+ * This test validates that the abort-at-ip value is within the address range of the
+ * rseq critical section.
+ *
+ * The test test_abort_at_ip_undo() validates that when aborted between two
+ * consecutive increments of two distinct variables, those variables are indeed one
+ * value apart.  This validates that abort undo operations based on the abort-at-ip
+ * work as expected.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "rseq.h"
+
+const int nr_iter = 10;
+
+#ifdef __x86_64__
+static void test_abort_at_ip_loop(void)
+{
+	void *abort_ip_addr, *abort_ip_start, *abort_ip_end;
+
+	printf("Testing abort_at_ip infinite loop\n");
+
+	__asm__ __volatile__ goto (
+		__RSEQ_ASM_DEFINE_TABLE(3, 0x0, ASM_RSEQ_CS_FLAG_ABORT_AT_IP, 1f, (2f - 1f), 4f) /* start, post_commit_offset, abort */
+		/* Start rseq by storing table entry pointer into rseq_cs. */
+		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, RSEQ_CS_OFFSET(%[rseq_abi]))
+		"rep; nop\n\t"	/* cpu_relax for busy loop. */
+		"jmp 1b\n\t"	/* infinite loop. */
+		"2:\n\t"
+		RSEQ_ASM_DEFINE_ABORT(4,
+			/* abort-at-ip must be pop from the stack. */
+			"popq %%rcx\n\t"
+			"addq $128, %%rsp\n\t"	/* x86-64 redzone */
+			"movq %%rcx, %[abort_ip_addr]\n\t"
+			"lea 1b(%%rip), %%rcx\n\t"
+			"movq %%rcx, %[abort_ip_start]\n\t"
+			"lea 2b(%%rip), %%rcx\n\t"
+			"movq %%rcx, %[abort_ip_end]\n\t",
+			abort)
+		: /* gcc asm goto does not allow outputs */
+		: [rseq_abi]		"r" (&__rseq_abi),
+		  [abort_ip_addr]	"m" (abort_ip_addr),
+		  [abort_ip_start]	"m" (abort_ip_start),
+		  [abort_ip_end]	"m" (abort_ip_end)
+		: "memory", "cc", "rcx"
+		: abort
+	);
+	fprintf(stderr, "Error: infinite loop should never exit gracefully.\n");
+	abort();
+
+abort:
+	printf("Critical section aborted (as expected) at ip %p, within range [%p,%p[\n",
+			abort_ip_addr, abort_ip_start, abort_ip_end);
+	if (abort_ip_addr < abort_ip_start || abort_ip_addr >= abort_ip_end) {
+		fprintf(stderr, "Error: abort-ip is outside of expected range\n");
+		abort();
+	}
+}
+
+static void test_abort_at_ip_undo(void)
+{
+	void *abort_ip_addr, *abort_ip_start, *abort_ip_end, *ip_after_first_inc, *ip_after_second_inc;
+	unsigned long v[2] = { 0, 0 };
+
+	printf("Testing abort_at_ip undo\n");
+
+	__asm__ __volatile__ goto (
+		__RSEQ_ASM_DEFINE_TABLE(3, 0x0, ASM_RSEQ_CS_FLAG_ABORT_AT_IP, 1f, (2f - 1f), 4f) /* start, post_commit_offset, abort */
+		/* Start rseq by storing table entry pointer into rseq_cs. */
+		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, RSEQ_CS_OFFSET(%[rseq_abi]))
+		"incq %[v0]\n\t"
+		"10:\n\t"
+		"rep; nop\n\t"
+		"incq %[v1]\n\t"
+		"20:\n\t"
+		"rep; nop\n\t"
+		"jmp 1b\n\t"	/* infinite loop. */
+		"2:\n\t"
+		RSEQ_ASM_DEFINE_ABORT(4,
+			/* abort-at-ip must be pop from the stack. */
+			"popq %%rcx\n\t"
+			"addq $128, %%rsp\n\t"	/* x86-64 redzone */
+			"movq %%rcx, %[abort_ip_addr]\n\t"
+			"lea 1b(%%rip), %%rcx\n\t"
+			"movq %%rcx, %[abort_ip_start]\n\t"
+			"lea 2b(%%rip), %%rcx\n\t"
+			"movq %%rcx, %[abort_ip_end]\n\t"
+			"lea 10b(%%rip), %%rcx\n\t"
+			"movq %%rcx, %[ip_after_first_inc]\n\t"
+			"lea 20b(%%rip), %%rcx\n\t"
+			"movq %%rcx, %[ip_after_second_inc]\n\t",
+			abort)
+		: /* gcc asm goto does not allow outputs */
+		: [rseq_abi]		"r" (&__rseq_abi),
+		  [abort_ip_addr]	"m" (abort_ip_addr),
+		  [abort_ip_start]	"m" (abort_ip_start),
+		  [abort_ip_end]	"m" (abort_ip_end),
+		  [ip_after_first_inc]	"m" (ip_after_first_inc),
+		  [ip_after_second_inc]	"m" (ip_after_second_inc),
+		  [v0]			"m" (v[0]),
+		  [v1]			"m" (v[1])
+		: "memory", "cc", "rcx"
+		: abort
+	);
+	fprintf(stderr, "Error: infinite loop should never exit gracefully.\n");
+	abort();
+
+abort:
+	printf("Critical section aborted (as expected) at ip %p, within range [%p,%p[\n",
+			abort_ip_addr, abort_ip_start, abort_ip_end);
+	if (abort_ip_addr < abort_ip_start || abort_ip_addr >= abort_ip_end) {
+		fprintf(stderr, "Error: abort-ip is outside of expected range\n");
+		abort();
+	}
+	printf("ip after first inc: %p, ip after second inc: %p\n",
+			ip_after_first_inc, ip_after_second_inc);
+	printf("Counter values: v0: %lu v1: %lu\n", v[0], v[1]);
+	if (abort_ip_addr < ip_after_first_inc || abort_ip_addr >= ip_after_second_inc) {
+		if (v[0] != v[1])
+			abort();
+	} else {
+		if (v[0] != v[1] + 1)
+			abort();
+	}
+}
+#elif defined (__i386__)
+static void test_abort_at_ip_loop(void)
+{
+	void *abort_ip_addr, *abort_ip_start, *abort_ip_end;
+
+	printf("Testing abort_at_ip infinite loop\n");
+
+	__asm__ __volatile__ goto (
+		__RSEQ_ASM_DEFINE_TABLE(3, 0x0, ASM_RSEQ_CS_FLAG_ABORT_AT_IP, 1f, (2f - 1f), 4f) /* start, post_commit_offset, abort */
+		/* Start rseq by storing table entry pointer into rseq_cs. */
+		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, RSEQ_CS_OFFSET(%[rseq_abi]))
+		"rep; nop\n\t"	/* cpu_relax for busy loop. */
+		"jmp 1b\n\t"	/* infinite loop. */
+		"2:\n\t"
+		RSEQ_ASM_DEFINE_ABORT(4,
+			/* abort-at-ip must be pop from the stack. */
+			"popl %%ecx\n\t"
+			"movl %%ecx, %[abort_ip_addr]\n\t"
+			"movl $1b, %%ecx\n\t"
+			"movl %%ecx, %[abort_ip_start]\n\t"
+			"movl $2b, %%ecx\n\t"
+			"movl %%ecx, %[abort_ip_end]\n\t",
+			abort)
+		: /* gcc asm goto does not allow outputs */
+		: [rseq_abi]		"r" (&__rseq_abi),
+		  [abort_ip_addr]	"m" (abort_ip_addr),
+		  [abort_ip_start]	"m" (abort_ip_start),
+		  [abort_ip_end]	"m" (abort_ip_end)
+		: "memory", "cc", "ecx"
+		: abort
+	);
+	fprintf(stderr, "Error: infinite loop should never exit gracefully.\n");
+	abort();
+
+abort:
+	printf("Critical section aborted (as expected) at ip %p, within range [%p,%p[\n",
+			abort_ip_addr, abort_ip_start, abort_ip_end);
+	if (abort_ip_addr < abort_ip_start || abort_ip_addr >= abort_ip_end) {
+		fprintf(stderr, "Error: abort-ip is outside of expected range\n");
+		abort();
+	}
+}
+
+static void test_abort_at_ip_undo(void)
+{
+	void *abort_ip_addr, *abort_ip_start, *abort_ip_end, *ip_after_first_inc, *ip_after_second_inc;
+	unsigned long v[2] = { 0, 0 };
+
+	printf("Testing abort_at_ip undo\n");
+
+	__asm__ __volatile__ goto (
+		__RSEQ_ASM_DEFINE_TABLE(3, 0x0, ASM_RSEQ_CS_FLAG_ABORT_AT_IP, 1f, (2f - 1f), 4f) /* start, post_commit_offset, abort */
+		/* Start rseq by storing table entry pointer into rseq_cs. */
+		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, RSEQ_CS_OFFSET(%[rseq_abi]))
+		"incl %[v0]\n\t"
+		"10:\n\t"
+		"rep; nop\n\t"
+		"incl %[v1]\n\t"
+		"20:\n\t"
+		"rep; nop\n\t"
+		"jmp 1b\n\t"	/* infinite loop. */
+		"2:\n\t"
+		RSEQ_ASM_DEFINE_ABORT(4,
+			/* abort-at-ip must be pop from the stack. */
+			"popl %%ecx\n\t"
+			"movl %%ecx, %[abort_ip_addr]\n\t"
+			"movl $1b, %%ecx\n\t"
+			"movl %%ecx, %[abort_ip_start]\n\t"
+			"movl $2b, %%ecx\n\t"
+			"movl %%ecx, %[abort_ip_end]\n\t"
+			"movl $10b, %%ecx\n\t"
+			"movl %%ecx, %[ip_after_first_inc]\n\t"
+			"movl $20b, %%ecx\n\t"
+			"movl %%ecx, %[ip_after_second_inc]\n\t",
+			abort)
+		: /* gcc asm goto does not allow outputs */
+		: [rseq_abi]		"r" (&__rseq_abi),
+		  [abort_ip_addr]	"m" (abort_ip_addr),
+		  [abort_ip_start]	"m" (abort_ip_start),
+		  [abort_ip_end]	"m" (abort_ip_end),
+		  [ip_after_first_inc]	"m" (ip_after_first_inc),
+		  [ip_after_second_inc]	"m" (ip_after_second_inc),
+		  [v0]			"m" (v[0]),
+		  [v1]			"m" (v[1])
+		: "memory", "cc", "rcx"
+		: abort
+	);
+	fprintf(stderr, "Error: infinite loop should never exit gracefully.\n");
+	abort();
+
+abort:
+	printf("Critical section aborted (as expected) at ip %p, within range [%p,%p[\n",
+			abort_ip_addr, abort_ip_start, abort_ip_end);
+	if (abort_ip_addr < abort_ip_start || abort_ip_addr >= abort_ip_end) {
+		fprintf(stderr, "Error: abort-ip is outside of expected range\n");
+		abort();
+	}
+	printf("ip after first inc: %p, ip after second inc: %p\n",
+			ip_after_first_inc, ip_after_second_inc);
+	printf("Counter values: v0: %lu v1: %lu\n", v[0], v[1]);
+	if (abort_ip_addr < ip_after_first_inc || abort_ip_addr >= ip_after_second_inc) {
+		if (v[0] != v[1])
+			abort();
+	} else {
+		if (v[0] != v[1] + 1)
+			abort();
+	}
+}
+#else
+static void test_abort_at_ip_loop(void)
+{
+	abort();
+}
+static void test_abort_at_ip_undo(void)
+{
+	abort();
+}
+#endif
+
+int main(int argc, char **argv)
+{
+	int i;
+
+	if (rseq_register_current_thread()) {
+		fprintf(stderr, "Error: rseq_register_current_thread(...) failed(%d): %s\n",
+			errno, strerror(errno));
+		goto init_thread_error;
+	}
+	printf("testing abort-at-ip extension\n");
+	if (rseq_query_extension(RSEQ_EXT_ABORT_AT_IP) != 0) {
+		fprintf(stderr, "RSEQ abort-at-ip extension is not supported, skipping test.\n");
+		return 0;
+	}
+	for (i = 0; i < nr_iter; i++)
+		test_abort_at_ip_loop();
+	for (i = 0; i < nr_iter; i++)
+		test_abort_at_ip_undo();
+	if (rseq_unregister_current_thread()) {
+		fprintf(stderr, "Error: rseq_unregister_current_thread(...) failed(%d): %s\n",
+			errno, strerror(errno));
+		goto init_thread_error;
+	}
+	return 0;
+
+init_thread_error:
+	return -1;
+}
-- 
2.17.1


  reply	other threads:[~2022-01-10 17:16 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-01-10 17:16 [RFC PATCH v2 1/2] rseq: x86: implement abort-at-ip extension Mathieu Desnoyers
2022-01-10 17:16 ` Mathieu Desnoyers [this message]
2022-01-11  5:31 ` kernel test robot
2022-01-11 11:05 ` Christian Brauner
2022-01-11 17:43   ` Mathieu Desnoyers
2022-01-12  8:46     ` Christian Brauner
2022-01-12 14:47       ` Mathieu Desnoyers
2022-01-12 14:55         ` Christian Brauner
2022-01-12 14:58         ` David Laight
2022-01-12 15:05           ` Mathieu Desnoyers
2022-01-12 15:15             ` David Laight
2022-01-12 15:24               ` Mathieu Desnoyers
2022-01-12 15:34               ` Peter Zijlstra
2022-01-12 15:53                 ` David Laight
2022-01-12 19:03 ` kernel test robot
2022-01-12 19:03   ` kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220110171611.8351-2-mathieu.desnoyers@efficios.com \
    --to=mathieu.desnoyers@efficios.com \
    --cc=David.Laight@ACULAB.COM \
    --cc=boqun.feng@gmail.com \
    --cc=carlos@redhat.com \
    --cc=christian.brauner@ubuntu.com \
    --cc=fw@deneb.enyo.de \
    --cc=hpa@zytor.com \
    --cc=linux-api@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=paulmck@kernel.org \
    --cc=peterz@infradead.org \
    --cc=pjt@google.com \
    --cc=shuah@kernel.org \
    --cc=tglx@linutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.