public inbox for linux-kernel@vger.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: 13+ 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 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

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox