xref: /linux/tools/testing/selftests/arm64/abi/ptrace.c (revision 8a922b7728a93d837954315c98b84f6b78de0c4f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2022 ARM Limited.
4  */
5 #include <errno.h>
6 #include <stdbool.h>
7 #include <stddef.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <sys/auxv.h>
13 #include <sys/prctl.h>
14 #include <sys/ptrace.h>
15 #include <sys/types.h>
16 #include <sys/uio.h>
17 #include <sys/wait.h>
18 #include <asm/sigcontext.h>
19 #include <asm/ptrace.h>
20 
21 #include "../../kselftest.h"
22 
23 #define EXPECTED_TESTS 7
24 
25 #define MAX_TPIDRS 2
26 
27 static bool have_sme(void)
28 {
29 	return getauxval(AT_HWCAP2) & HWCAP2_SME;
30 }
31 
32 static void test_tpidr(pid_t child)
33 {
34 	uint64_t read_val[MAX_TPIDRS];
35 	uint64_t write_val[MAX_TPIDRS];
36 	struct iovec read_iov, write_iov;
37 	bool test_tpidr2 = false;
38 	int ret, i;
39 
40 	read_iov.iov_base = read_val;
41 	write_iov.iov_base = write_val;
42 
43 	/* Should be able to read a single TPIDR... */
44 	read_iov.iov_len = sizeof(uint64_t);
45 	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
46 	ksft_test_result(ret == 0, "read_tpidr_one\n");
47 
48 	/* ...write a new value.. */
49 	write_iov.iov_len = sizeof(uint64_t);
50 	write_val[0] = read_val[0]++;
51 	ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_TLS, &write_iov);
52 	ksft_test_result(ret == 0, "write_tpidr_one\n");
53 
54 	/* ...then read it back */
55 	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
56 	ksft_test_result(ret == 0 && write_val[0] == read_val[0],
57 			 "verify_tpidr_one\n");
58 
59 	/* If we have TPIDR2 we should be able to read it */
60 	read_iov.iov_len = sizeof(read_val);
61 	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
62 	if (ret == 0) {
63 		/* If we have SME there should be two TPIDRs */
64 		if (read_iov.iov_len >= sizeof(read_val))
65 			test_tpidr2 = true;
66 
67 		if (have_sme() && test_tpidr2) {
68 			ksft_test_result(test_tpidr2, "count_tpidrs\n");
69 		} else {
70 			ksft_test_result(read_iov.iov_len % sizeof(uint64_t) == 0,
71 					 "count_tpidrs\n");
72 		}
73 	} else {
74 		ksft_test_result_fail("count_tpidrs\n");
75 	}
76 
77 	if (test_tpidr2) {
78 		/* Try to write new values to all known TPIDRs... */
79 		write_iov.iov_len = sizeof(write_val);
80 		for (i = 0; i < MAX_TPIDRS; i++)
81 			write_val[i] = read_val[i] + 1;
82 		ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_TLS, &write_iov);
83 
84 		ksft_test_result(ret == 0 &&
85 				 write_iov.iov_len == sizeof(write_val),
86 				 "tpidr2_write\n");
87 
88 		/* ...then read them back */
89 		read_iov.iov_len = sizeof(read_val);
90 		ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
91 
92 		if (have_sme()) {
93 			/* Should read back the written value */
94 			ksft_test_result(ret == 0 &&
95 					 read_iov.iov_len >= sizeof(read_val) &&
96 					 memcmp(read_val, write_val,
97 						sizeof(read_val)) == 0,
98 					 "tpidr2_read\n");
99 		} else {
100 			/* TPIDR2 should read as zero */
101 			ksft_test_result(ret == 0 &&
102 					 read_iov.iov_len >= sizeof(read_val) &&
103 					 read_val[0] == write_val[0] &&
104 					 read_val[1] == 0,
105 					 "tpidr2_read\n");
106 		}
107 
108 		/* Writing only TPIDR... */
109 		write_iov.iov_len = sizeof(uint64_t);
110 		memcpy(write_val, read_val, sizeof(read_val));
111 		write_val[0] += 1;
112 		ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_TLS, &write_iov);
113 
114 		if (ret == 0) {
115 			/* ...should leave TPIDR2 untouched */
116 			read_iov.iov_len = sizeof(read_val);
117 			ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS,
118 				     &read_iov);
119 
120 			ksft_test_result(ret == 0 &&
121 					 read_iov.iov_len >= sizeof(read_val) &&
122 					 memcmp(read_val, write_val,
123 						sizeof(read_val)) == 0,
124 					 "write_tpidr_only\n");
125 		} else {
126 			ksft_test_result_fail("write_tpidr_only\n");
127 		}
128 	} else {
129 		ksft_test_result_skip("tpidr2_write\n");
130 		ksft_test_result_skip("tpidr2_read\n");
131 		ksft_test_result_skip("write_tpidr_only\n");
132 	}
133 }
134 
135 static int do_child(void)
136 {
137 	if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
138 		ksft_exit_fail_msg("PTRACE_TRACEME", strerror(errno));
139 
140 	if (raise(SIGSTOP))
141 		ksft_exit_fail_msg("raise(SIGSTOP)", strerror(errno));
142 
143 	return EXIT_SUCCESS;
144 }
145 
146 static int do_parent(pid_t child)
147 {
148 	int ret = EXIT_FAILURE;
149 	pid_t pid;
150 	int status;
151 	siginfo_t si;
152 
153 	/* Attach to the child */
154 	while (1) {
155 		int sig;
156 
157 		pid = wait(&status);
158 		if (pid == -1) {
159 			perror("wait");
160 			goto error;
161 		}
162 
163 		/*
164 		 * This should never happen but it's hard to flag in
165 		 * the framework.
166 		 */
167 		if (pid != child)
168 			continue;
169 
170 		if (WIFEXITED(status) || WIFSIGNALED(status))
171 			ksft_exit_fail_msg("Child died unexpectedly\n");
172 
173 		if (!WIFSTOPPED(status))
174 			goto error;
175 
176 		sig = WSTOPSIG(status);
177 
178 		if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
179 			if (errno == ESRCH)
180 				goto disappeared;
181 
182 			if (errno == EINVAL) {
183 				sig = 0; /* bust group-stop */
184 				goto cont;
185 			}
186 
187 			ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n",
188 					      strerror(errno));
189 			goto error;
190 		}
191 
192 		if (sig == SIGSTOP && si.si_code == SI_TKILL &&
193 		    si.si_pid == pid)
194 			break;
195 
196 	cont:
197 		if (ptrace(PTRACE_CONT, pid, NULL, sig)) {
198 			if (errno == ESRCH)
199 				goto disappeared;
200 
201 			ksft_test_result_fail("PTRACE_CONT: %s\n",
202 					      strerror(errno));
203 			goto error;
204 		}
205 	}
206 
207 	ksft_print_msg("Parent is %d, child is %d\n", getpid(), child);
208 
209 	test_tpidr(child);
210 
211 	ret = EXIT_SUCCESS;
212 
213 error:
214 	kill(child, SIGKILL);
215 
216 disappeared:
217 	return ret;
218 }
219 
220 int main(void)
221 {
222 	int ret = EXIT_SUCCESS;
223 	pid_t child;
224 
225 	srandom(getpid());
226 
227 	ksft_print_header();
228 
229 	ksft_set_plan(EXPECTED_TESTS);
230 
231 	child = fork();
232 	if (!child)
233 		return do_child();
234 
235 	if (do_parent(child))
236 		ret = EXIT_FAILURE;
237 
238 	ksft_print_cnts();
239 
240 	return ret;
241 }
242