The test creates two processes where one traces another one. The tracee executes a system call, the tracer traps it, changes orig_x0, triggers a signal and checks that the syscall is restarted with the setted argument.
Test output: $ ./ptrace_restart_syscall_test 1..3 ok 1 orig_x0: 0x3 ok 2 x0: 0x5 ok 3 The child exited with code 0. # Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Andrei Vagin <ava...@gmail.com> --- tools/testing/selftests/arm64/ptrace/Makefile | 6 + tools/testing/selftests/arm64/ptrace/lib.h | 36 ++++++ .../ptrace/ptrace_restart_syscall_test.c | 122 ++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 tools/testing/selftests/arm64/ptrace/Makefile create mode 100644 tools/testing/selftests/arm64/ptrace/lib.h create mode 100644 tools/testing/selftests/arm64/ptrace/ptrace_restart_syscall_test.c diff --git a/tools/testing/selftests/arm64/ptrace/Makefile b/tools/testing/selftests/arm64/ptrace/Makefile new file mode 100644 index 000000000000..1bc10e2d2ac8 --- /dev/null +++ b/tools/testing/selftests/arm64/ptrace/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS += -g -I../../../../../usr/include/ +TEST_GEN_PROGS := ptrace_restart_syscall_test + +include ../../lib.mk diff --git a/tools/testing/selftests/arm64/ptrace/lib.h b/tools/testing/selftests/arm64/ptrace/lib.h new file mode 100644 index 000000000000..14f4737188a3 --- /dev/null +++ b/tools/testing/selftests/arm64/ptrace/lib.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +#ifndef __PTRACE_TEST_LOG_H +#define __PTRACE_TEST_LOG_H + +#define pr_p(func, fmt, ...) func("%s:%d: " fmt ": %m", \ + __func__, __LINE__, ##__VA_ARGS__) + +#define pr_err(fmt, ...) \ + ({ \ + ksft_test_result_error(fmt "\n", ##__VA_ARGS__); \ + -1; \ + }) + +#define pr_fail(fmt, ...) \ + ({ \ + ksft_test_result_fail(fmt "\n", ##__VA_ARGS__); \ + -1; \ + }) + +#define pr_perror(fmt, ...) pr_p(pr_err, fmt, ##__VA_ARGS__) + +static inline int ptrace_and_wait(pid_t pid, int cmd, int sig) +{ + int status; + + /* Stop on syscall-exit. */ + if (ptrace(cmd, pid, 0, 0)) + return pr_perror("Can't resume the child %d", pid); + if (waitpid(pid, &status, 0) != pid) + return pr_perror("Can't wait for the child %d", pid); + if (!WIFSTOPPED(status) || WSTOPSIG(status) != sig) + return pr_err("Unexpected status: %x", status); + return 0; +} + +#endif diff --git a/tools/testing/selftests/arm64/ptrace/ptrace_restart_syscall_test.c b/tools/testing/selftests/arm64/ptrace/ptrace_restart_syscall_test.c new file mode 100644 index 000000000000..ce59657f41be --- /dev/null +++ b/tools/testing/selftests/arm64/ptrace/ptrace_restart_syscall_test.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <sys/uio.h> +#include <linux/elf.h> +#include <linux/unistd.h> + +#include "../../kselftest.h" +#include "lib.h" + +static int child(int fd) +{ + char c; + + if (read(fd, &c, 1) != 1) + return 1; + + return 0; +} + +int main(int argc, void **argv) +{ + union { + struct user_regs_struct r; + struct { + char __regs[272]; + unsigned long long orig_x0; + unsigned long long orig_x7; + }; + } regs = {}; + struct iovec iov = { + .iov_base = ®s, + .iov_len = sizeof(regs), + }; + int status; + pid_t pid; + int p[2], fdzero; + + ksft_set_plan(3); + + if (pipe(p)) + return pr_perror("Can't create a pipe"); + fdzero = open("/dev/zero", O_RDONLY); + if (fdzero < 0) + return pr_perror("Can't open /dev/zero"); + + pid = fork(); + if (pid == 0) { + kill(getpid(), SIGSTOP); + return child(p[0]); + } + if (pid < 0) + return 1; + + if (ptrace(PTRACE_ATTACH, pid, 0, 0)) + return pr_perror("Can't attach to the child %d", pid); + if (waitpid(pid, &status, 0) != pid) + return pr_perror("Can't wait for the child %d", pid); + /* Skip SIGSTOP */ + if (ptrace_and_wait(pid, PTRACE_CONT, SIGSTOP)) + return 1; + + /* Resume the child to the next system call. */ + if (ptrace_and_wait(pid, PTRACE_SYSCALL, SIGTRAP)) + return 1; + + /* Send a signal to interrupt the system call. */ + kill(pid, SIGUSR1); + + /* Stop on syscall-exit. */ + if (ptrace_and_wait(pid, PTRACE_SYSCALL, SIGTRAP)) + return 1; + + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)) + return pr_perror("Can't get child registers"); + if (regs.orig_x0 != p[0]) + return pr_fail("Unexpected x0: 0x%lx", regs.r.regs[0]); + ksft_test_result_pass("orig_x0: 0x%llx\n", regs.orig_x0); + + /* Change orig_x0 that will be x0 for the restarted system call. */ + regs.orig_x0 = fdzero; + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)) + return pr_perror("Can't get child registers"); + + /* Trap the signal and skip it. */ + if (ptrace_and_wait(pid, PTRACE_SYSCALL, SIGUSR1)) + return 1; + + /* Trap the restarted system call. */ + if (ptrace_and_wait(pid, PTRACE_SYSCALL, SIGTRAP)) + return 1; + + /* Check that the syscall is started with the right first argument. */ + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)) + return pr_perror("Can't get child registers"); + if (regs.r.regs[0] != fdzero) + return pr_fail("unexpected x0: %lx", regs.r.regs[0]); + ksft_test_result_pass("x0: 0x%llx\n", regs.r.regs[0]); + + if (ptrace(PTRACE_CONT, pid, 0, 0)) + return pr_perror("Can't resume the child %d", pid); + if (waitpid(pid, &status, 0) != pid) + return pr_perror("Can't wait for the child %d", pid); + if (status != 0) + return pr_fail("Child exited with code %d.", status); + + ksft_test_result_pass("The child exited with code 0.\n"); + ksft_exit_pass(); + return 0; +} + -- 2.29.2