[PATCH v 2/2] selftests: riscv: Add test for the Vector ptrace interface

Yong-Xuan Wang posted 2 patches 10 hours ago
[PATCH v 2/2] selftests: riscv: Add test for the Vector ptrace interface
Posted by Yong-Xuan Wang 10 hours ago
Add a test case that does some basic verification of the Vector ptrace
interface. This forks a child process then using ptrace to inspect and
manipulate the v31 register of the child.

Signed-off-by: Yong-Xuan Wang <yongxuan.wang@sifive.com>
---
 tools/testing/selftests/riscv/vector/Makefile |   5 +-
 .../selftests/riscv/vector/vstate_ptrace.c    | 132 ++++++++++++++++++
 2 files changed, 136 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/riscv/vector/vstate_ptrace.c

diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile
index 6f7497f4e7b3..45f25e9dd264 100644
--- a/tools/testing/selftests/riscv/vector/Makefile
+++ b/tools/testing/selftests/riscv/vector/Makefile
@@ -2,7 +2,7 @@
 # Copyright (C) 2021 ARM Limited
 # Originally tools/testing/arm64/abi/Makefile
 
-TEST_GEN_PROGS := v_initval vstate_prctl
+TEST_GEN_PROGS := v_initval vstate_prctl vsate_ptrace
 TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
 
 include ../../lib.mk
@@ -26,3 +26,6 @@ $(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o
 $(OUTPUT)/v_exec_initval_nolibc: v_exec_initval_nolibc.c
 	$(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \
 		-Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc
+
+$(OUTPUT)/vstate_ptrace: vstate_ptrace.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o
+	$(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
diff --git a/tools/testing/selftests/riscv/vector/vstate_ptrace.c b/tools/testing/selftests/riscv/vector/vstate_ptrace.c
new file mode 100644
index 000000000000..8a7bcf318e59
--- /dev/null
+++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stdio.h>
+#include <stdlib.h>
+#include <asm/ptrace.h>
+#include <linux/elf.h>
+#include <sys/ptrace.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include "../../kselftest.h"
+#include "v_helpers.h"
+
+int parent_set_val, child_set_val;
+
+static long do_ptrace(enum __ptrace_request op, pid_t pid, long type, size_t size, void *data)
+{
+	struct iovec v_iovec = {
+		.iov_len = size,
+		.iov_base = data
+	};
+
+	return ptrace(op, pid, type, &v_iovec);
+}
+
+static int do_child(void)
+{
+	int out;
+
+	if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) {
+		ksft_perror("PTRACE_TRACEME failed\n");
+		return EXIT_FAILURE;
+	}
+
+	asm volatile (".option push\n\t"
+		".option	arch, +v\n\t"
+		"vsetivli	x0, 1, e32, m1, ta, ma\n\t"
+		"vmv.s.x	v31, %[in]\n\t"
+		"ebreak\n\t"
+		"vmv.x.s	%[out], v31\n\t"
+		".option pop\n\t"
+		: [out] "=r" (out)
+		: [in] "r" (child_set_val));
+
+	if (out != parent_set_val)
+		return EXIT_FAILURE;
+
+	return EXIT_SUCCESS;
+}
+
+static void do_parent(pid_t child)
+{
+	int status;
+	void *data = NULL;
+
+	/* Attach to the child */
+	while (waitpid(child, &status, 0)) {
+		if (WIFEXITED(status)) {
+			ksft_test_result(WEXITSTATUS(status) == 0, "SETREGSET vector\n");
+			goto out;
+		} else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) {
+			size_t size, t;
+			void *data, *v31;
+			struct __riscv_v_regset_state *v_regset_hdr;
+			struct user_regs_struct *gpreg;
+
+			size = sizeof(*v_regset_hdr);
+			data = malloc(size);
+			if (!data)
+				goto out;
+			v_regset_hdr = (struct __riscv_v_regset_state *)data;
+
+			if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data))
+				goto out;
+
+			ksft_print_msg("vlenb %ld\n", v_regset_hdr->vlenb);
+			data = realloc(data, size + v_regset_hdr->vlenb * 32);
+			if (!data)
+				goto out;
+			v31 = (void *)(data + size + v_regset_hdr->vlenb * 31);
+			size += v_regset_hdr->vlenb * 32;
+
+			if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data))
+				goto out;
+
+			ksft_test_result(*(int *)v31 == child_set_val, "GETREGSET vector\n");
+
+			*(int *)v31 = parent_set_val;
+			if (do_ptrace(PTRACE_SETREGSET, child, NT_RISCV_VECTOR, size, data))
+				goto out;
+
+			/* move the pc forward */
+			size = sizeof(*gpreg);
+			data = realloc(data, size);
+			gpreg = (struct user_regs_struct *)data;
+
+			if (do_ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, size, data))
+				goto out;
+
+			gpreg->pc += 2;
+			if (do_ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, size, data))
+				goto out;
+		}
+
+		ptrace(PTRACE_CONT, child, NULL, NULL);
+	}
+
+out:
+	free(data);
+}
+
+int main(void)
+{
+	pid_t child;
+
+	ksft_set_plan(2);
+	if (!is_vector_supported() && !is_xtheadvector_supported())
+		ksft_exit_skip("Vector not supported\n");
+
+	srandom(getpid());
+	parent_set_val = rand();
+	child_set_val = rand();
+
+	child = fork();
+	if (child < 0)
+		ksft_exit_fail_msg("Fork failed %d\n", child);
+
+	if (!child)
+		return do_child();
+
+	do_parent(child);
+
+	ksft_finished();
+}
-- 
2.43.0
Re: [PATCH v 2/2] selftests: riscv: Add test for the Vector ptrace interface
Posted by Andy Chiu 6 hours ago
Hi Yong-Xuan,

On Wed, Oct 1, 2025 at 6:15 AM Yong-Xuan Wang <yongxuan.wang@sifive.com> wrote:
>
> Add a test case that does some basic verification of the Vector ptrace
> interface. This forks a child process then using ptrace to inspect and
> manipulate the v31 register of the child.
>
> Signed-off-by: Yong-Xuan Wang <yongxuan.wang@sifive.com>
> ---
>  tools/testing/selftests/riscv/vector/Makefile |   5 +-
>  .../selftests/riscv/vector/vstate_ptrace.c    | 132 ++++++++++++++++++
>  2 files changed, 136 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/riscv/vector/vstate_ptrace.c
>
> diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile
> index 6f7497f4e7b3..45f25e9dd264 100644
> --- a/tools/testing/selftests/riscv/vector/Makefile
> +++ b/tools/testing/selftests/riscv/vector/Makefile
> @@ -2,7 +2,7 @@
>  # Copyright (C) 2021 ARM Limited
>  # Originally tools/testing/arm64/abi/Makefile
>
> -TEST_GEN_PROGS := v_initval vstate_prctl
> +TEST_GEN_PROGS := v_initval vstate_prctl vsate_ptrace
>  TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
>
>  include ../../lib.mk
> @@ -26,3 +26,6 @@ $(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o
>  $(OUTPUT)/v_exec_initval_nolibc: v_exec_initval_nolibc.c
>         $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \
>                 -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc
> +
> +$(OUTPUT)/vstate_ptrace: vstate_ptrace.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o
> +       $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
> diff --git a/tools/testing/selftests/riscv/vector/vstate_ptrace.c b/tools/testing/selftests/riscv/vector/vstate_ptrace.c
> new file mode 100644
> index 000000000000..8a7bcf318e59
> --- /dev/null
> +++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c
> @@ -0,0 +1,132 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <asm/ptrace.h>
> +#include <linux/elf.h>
> +#include <sys/ptrace.h>
> +#include <sys/uio.h>
> +#include <sys/wait.h>
> +#include "../../kselftest.h"
> +#include "v_helpers.h"
> +
> +int parent_set_val, child_set_val;
> +
> +static long do_ptrace(enum __ptrace_request op, pid_t pid, long type, size_t size, void *data)
> +{
> +       struct iovec v_iovec = {
> +               .iov_len = size,
> +               .iov_base = data
> +       };
> +
> +       return ptrace(op, pid, type, &v_iovec);
> +}
> +
> +static int do_child(void)
> +{
> +       int out;
> +
> +       if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) {
> +               ksft_perror("PTRACE_TRACEME failed\n");
> +               return EXIT_FAILURE;
> +       }
> +
> +       asm volatile (".option push\n\t"
> +               ".option        arch, +v\n\t"
> +               "vsetivli       x0, 1, e32, m1, ta, ma\n\t"
> +               "vmv.s.x        v31, %[in]\n\t"
> +               "ebreak\n\t"
> +               "vmv.x.s        %[out], v31\n\t"
> +               ".option pop\n\t"
> +               : [out] "=r" (out)
> +               : [in] "r" (child_set_val));
> +
> +       if (out != parent_set_val)
> +               return EXIT_FAILURE;
> +
> +       return EXIT_SUCCESS;
> +}
> +
> +static void do_parent(pid_t child)
> +{
> +       int status;
> +       void *data = NULL;
> +
> +       /* Attach to the child */
> +       while (waitpid(child, &status, 0)) {
> +               if (WIFEXITED(status)) {
> +                       ksft_test_result(WEXITSTATUS(status) == 0, "SETREGSET vector\n");
> +                       goto out;
> +               } else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) {
> +                       size_t size, t;
> +                       void *data, *v31;
> +                       struct __riscv_v_regset_state *v_regset_hdr;
> +                       struct user_regs_struct *gpreg;
> +
> +                       size = sizeof(*v_regset_hdr);
> +                       data = malloc(size);
> +                       if (!data)
> +                               goto out;
> +                       v_regset_hdr = (struct __riscv_v_regset_state *)data;
> +
> +                       if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data))
> +                               goto out;
> +
> +                       ksft_print_msg("vlenb %ld\n", v_regset_hdr->vlenb);
> +                       data = realloc(data, size + v_regset_hdr->vlenb * 32);
> +                       if (!data)
> +                               goto out;
> +                       v31 = (void *)(data + size + v_regset_hdr->vlenb * 31);
> +                       size += v_regset_hdr->vlenb * 32;
> +
> +                       if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data))
> +                               goto out;
> +
> +                       ksft_test_result(*(int *)v31 == child_set_val, "GETREGSET vector\n");
> +
> +                       *(int *)v31 = parent_set_val;
> +                       if (do_ptrace(PTRACE_SETREGSET, child, NT_RISCV_VECTOR, size, data))
> +                               goto out;
> +
> +                       /* move the pc forward */
> +                       size = sizeof(*gpreg);
> +                       data = realloc(data, size);
> +                       gpreg = (struct user_regs_struct *)data;
> +
> +                       if (do_ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, size, data))
> +                               goto out;
> +
> +                       gpreg->pc += 2;

Just nitpicking here, simply adding 2 may fail if the program is not
compiled with C. You may either +=4 and use ".option norvc" in the asm
or determine the size of ebreak by decoding it.

with or without the fix,

Reviewed-by: Andy Chiu <andybnac@gmail.com>