xref: /linux/tools/testing/selftests/arm64/abi/ptrace.c (revision 114143a595895c03fbefccfd8346fc51fb4908ed)
1ecaf4d3fSMark Brown // SPDX-License-Identifier: GPL-2.0-only
2ecaf4d3fSMark Brown /*
3ecaf4d3fSMark Brown  * Copyright (C) 2022 ARM Limited.
4ecaf4d3fSMark Brown  */
5ecaf4d3fSMark Brown #include <errno.h>
6ecaf4d3fSMark Brown #include <stdbool.h>
7ecaf4d3fSMark Brown #include <stddef.h>
8ecaf4d3fSMark Brown #include <stdio.h>
9ecaf4d3fSMark Brown #include <stdlib.h>
10ecaf4d3fSMark Brown #include <string.h>
11ecaf4d3fSMark Brown #include <unistd.h>
12ecaf4d3fSMark Brown #include <sys/auxv.h>
13ecaf4d3fSMark Brown #include <sys/prctl.h>
14ecaf4d3fSMark Brown #include <sys/ptrace.h>
15ecaf4d3fSMark Brown #include <sys/types.h>
16ecaf4d3fSMark Brown #include <sys/uio.h>
17ecaf4d3fSMark Brown #include <sys/wait.h>
18ecaf4d3fSMark Brown #include <asm/sigcontext.h>
19ecaf4d3fSMark Brown #include <asm/ptrace.h>
20ecaf4d3fSMark Brown 
21ecaf4d3fSMark Brown #include "../../kselftest.h"
22ecaf4d3fSMark Brown 
23cb5aa637SMark Brown #define EXPECTED_TESTS 11
24ecaf4d3fSMark Brown 
252ddadec2SMark Brown #define MAX_TPIDRS 2
26ecaf4d3fSMark Brown 
have_sme(void)27ecaf4d3fSMark Brown static bool have_sme(void)
28ecaf4d3fSMark Brown {
29ecaf4d3fSMark Brown 	return getauxval(AT_HWCAP2) & HWCAP2_SME;
30ecaf4d3fSMark Brown }
31ecaf4d3fSMark Brown 
test_tpidr(pid_t child)32ecaf4d3fSMark Brown static void test_tpidr(pid_t child)
33ecaf4d3fSMark Brown {
34ecaf4d3fSMark Brown 	uint64_t read_val[MAX_TPIDRS];
35ecaf4d3fSMark Brown 	uint64_t write_val[MAX_TPIDRS];
36ecaf4d3fSMark Brown 	struct iovec read_iov, write_iov;
372ddadec2SMark Brown 	bool test_tpidr2 = false;
382ddadec2SMark Brown 	int ret, i;
39ecaf4d3fSMark Brown 
40ecaf4d3fSMark Brown 	read_iov.iov_base = read_val;
41ecaf4d3fSMark Brown 	write_iov.iov_base = write_val;
42ecaf4d3fSMark Brown 
43ecaf4d3fSMark Brown 	/* Should be able to read a single TPIDR... */
44ecaf4d3fSMark Brown 	read_iov.iov_len = sizeof(uint64_t);
45ecaf4d3fSMark Brown 	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
46ecaf4d3fSMark Brown 	ksft_test_result(ret == 0, "read_tpidr_one\n");
47ecaf4d3fSMark Brown 
48ecaf4d3fSMark Brown 	/* ...write a new value.. */
49ecaf4d3fSMark Brown 	write_iov.iov_len = sizeof(uint64_t);
50031d1f20SDev Jain 	write_val[0] = read_val[0] + 1;
51ecaf4d3fSMark Brown 	ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_TLS, &write_iov);
52ecaf4d3fSMark Brown 	ksft_test_result(ret == 0, "write_tpidr_one\n");
53ecaf4d3fSMark Brown 
54ecaf4d3fSMark Brown 	/* ...then read it back */
55ecaf4d3fSMark Brown 	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
56ecaf4d3fSMark Brown 	ksft_test_result(ret == 0 && write_val[0] == read_val[0],
57ecaf4d3fSMark Brown 			 "verify_tpidr_one\n");
582ddadec2SMark Brown 
592ddadec2SMark Brown 	/* If we have TPIDR2 we should be able to read it */
602ddadec2SMark Brown 	read_iov.iov_len = sizeof(read_val);
612ddadec2SMark Brown 	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
622ddadec2SMark Brown 	if (ret == 0) {
632ddadec2SMark Brown 		/* If we have SME there should be two TPIDRs */
642ddadec2SMark Brown 		if (read_iov.iov_len >= sizeof(read_val))
652ddadec2SMark Brown 			test_tpidr2 = true;
662ddadec2SMark Brown 
672ddadec2SMark Brown 		if (have_sme() && test_tpidr2) {
682ddadec2SMark Brown 			ksft_test_result(test_tpidr2, "count_tpidrs\n");
692ddadec2SMark Brown 		} else {
702ddadec2SMark Brown 			ksft_test_result(read_iov.iov_len % sizeof(uint64_t) == 0,
712ddadec2SMark Brown 					 "count_tpidrs\n");
722ddadec2SMark Brown 		}
732ddadec2SMark Brown 	} else {
742ddadec2SMark Brown 		ksft_test_result_fail("count_tpidrs\n");
752ddadec2SMark Brown 	}
762ddadec2SMark Brown 
772ddadec2SMark Brown 	if (test_tpidr2) {
782ddadec2SMark Brown 		/* Try to write new values to all known TPIDRs... */
792ddadec2SMark Brown 		write_iov.iov_len = sizeof(write_val);
802ddadec2SMark Brown 		for (i = 0; i < MAX_TPIDRS; i++)
812ddadec2SMark Brown 			write_val[i] = read_val[i] + 1;
822ddadec2SMark Brown 		ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_TLS, &write_iov);
832ddadec2SMark Brown 
842ddadec2SMark Brown 		ksft_test_result(ret == 0 &&
852ddadec2SMark Brown 				 write_iov.iov_len == sizeof(write_val),
862ddadec2SMark Brown 				 "tpidr2_write\n");
872ddadec2SMark Brown 
882ddadec2SMark Brown 		/* ...then read them back */
892ddadec2SMark Brown 		read_iov.iov_len = sizeof(read_val);
902ddadec2SMark Brown 		ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov);
912ddadec2SMark Brown 
922ddadec2SMark Brown 		if (have_sme()) {
932ddadec2SMark Brown 			/* Should read back the written value */
942ddadec2SMark Brown 			ksft_test_result(ret == 0 &&
952ddadec2SMark Brown 					 read_iov.iov_len >= sizeof(read_val) &&
962ddadec2SMark Brown 					 memcmp(read_val, write_val,
972ddadec2SMark Brown 						sizeof(read_val)) == 0,
982ddadec2SMark Brown 					 "tpidr2_read\n");
992ddadec2SMark Brown 		} else {
1002ddadec2SMark Brown 			/* TPIDR2 should read as zero */
1012ddadec2SMark Brown 			ksft_test_result(ret == 0 &&
1022ddadec2SMark Brown 					 read_iov.iov_len >= sizeof(read_val) &&
1032ddadec2SMark Brown 					 read_val[0] == write_val[0] &&
1042ddadec2SMark Brown 					 read_val[1] == 0,
1052ddadec2SMark Brown 					 "tpidr2_read\n");
1062ddadec2SMark Brown 		}
1072ddadec2SMark Brown 
1082ddadec2SMark Brown 		/* Writing only TPIDR... */
1092ddadec2SMark Brown 		write_iov.iov_len = sizeof(uint64_t);
1102ddadec2SMark Brown 		memcpy(write_val, read_val, sizeof(read_val));
1112ddadec2SMark Brown 		write_val[0] += 1;
1122ddadec2SMark Brown 		ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_TLS, &write_iov);
1132ddadec2SMark Brown 
1142ddadec2SMark Brown 		if (ret == 0) {
1152ddadec2SMark Brown 			/* ...should leave TPIDR2 untouched */
1162ddadec2SMark Brown 			read_iov.iov_len = sizeof(read_val);
1172ddadec2SMark Brown 			ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS,
1182ddadec2SMark Brown 				     &read_iov);
1192ddadec2SMark Brown 
1202ddadec2SMark Brown 			ksft_test_result(ret == 0 &&
1212ddadec2SMark Brown 					 read_iov.iov_len >= sizeof(read_val) &&
1222ddadec2SMark Brown 					 memcmp(read_val, write_val,
1232ddadec2SMark Brown 						sizeof(read_val)) == 0,
1242ddadec2SMark Brown 					 "write_tpidr_only\n");
1252ddadec2SMark Brown 		} else {
1262ddadec2SMark Brown 			ksft_test_result_fail("write_tpidr_only\n");
1272ddadec2SMark Brown 		}
1282ddadec2SMark Brown 	} else {
1292ddadec2SMark Brown 		ksft_test_result_skip("tpidr2_write\n");
1302ddadec2SMark Brown 		ksft_test_result_skip("tpidr2_read\n");
1312ddadec2SMark Brown 		ksft_test_result_skip("write_tpidr_only\n");
1322ddadec2SMark Brown 	}
133ecaf4d3fSMark Brown }
134ecaf4d3fSMark Brown 
test_hw_debug(pid_t child,int type,const char * type_name)135cb5aa637SMark Brown static void test_hw_debug(pid_t child, int type, const char *type_name)
136cb5aa637SMark Brown {
137cb5aa637SMark Brown 	struct user_hwdebug_state state;
138cb5aa637SMark Brown 	struct iovec iov;
139cb5aa637SMark Brown 	int slots, arch, ret;
140cb5aa637SMark Brown 
141cb5aa637SMark Brown 	iov.iov_len = sizeof(state);
142cb5aa637SMark Brown 	iov.iov_base = &state;
143cb5aa637SMark Brown 
144cb5aa637SMark Brown 	/* Should be able to read the values */
145cb5aa637SMark Brown 	ret = ptrace(PTRACE_GETREGSET, child, type, &iov);
146cb5aa637SMark Brown 	ksft_test_result(ret == 0, "read_%s\n", type_name);
147cb5aa637SMark Brown 
148cb5aa637SMark Brown 	if (ret == 0) {
149cb5aa637SMark Brown 		/* Low 8 bits is the number of slots, next 4 bits the arch */
150cb5aa637SMark Brown 		slots = state.dbg_info & 0xff;
151cb5aa637SMark Brown 		arch = (state.dbg_info >> 8) & 0xf;
152cb5aa637SMark Brown 
153cb5aa637SMark Brown 		ksft_print_msg("%s version %d with %d slots\n", type_name,
154cb5aa637SMark Brown 			       arch, slots);
155cb5aa637SMark Brown 
156cb5aa637SMark Brown 		/* Zero is not currently architecturally valid */
157cb5aa637SMark Brown 		ksft_test_result(arch, "%s_arch_set\n", type_name);
158cb5aa637SMark Brown 	} else {
1590c35e3bdSRemington Brasga 		ksft_test_result_skip("%s_arch_set\n", type_name);
160cb5aa637SMark Brown 	}
161cb5aa637SMark Brown }
162cb5aa637SMark Brown 
do_child(void)163ecaf4d3fSMark Brown static int do_child(void)
164ecaf4d3fSMark Brown {
165ecaf4d3fSMark Brown 	if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
166*d736d4fcSDev Jain 		ksft_exit_fail_perror("PTRACE_TRACEME");
167ecaf4d3fSMark Brown 
168ecaf4d3fSMark Brown 	if (raise(SIGSTOP))
169*d736d4fcSDev Jain 		ksft_exit_fail_perror("raise(SIGSTOP)");
170ecaf4d3fSMark Brown 
171ecaf4d3fSMark Brown 	return EXIT_SUCCESS;
172ecaf4d3fSMark Brown }
173ecaf4d3fSMark Brown 
do_parent(pid_t child)174ecaf4d3fSMark Brown static int do_parent(pid_t child)
175ecaf4d3fSMark Brown {
176ecaf4d3fSMark Brown 	int ret = EXIT_FAILURE;
177ecaf4d3fSMark Brown 	pid_t pid;
178ecaf4d3fSMark Brown 	int status;
179ecaf4d3fSMark Brown 	siginfo_t si;
180ecaf4d3fSMark Brown 
181ecaf4d3fSMark Brown 	/* Attach to the child */
182ecaf4d3fSMark Brown 	while (1) {
183ecaf4d3fSMark Brown 		int sig;
184ecaf4d3fSMark Brown 
185ecaf4d3fSMark Brown 		pid = wait(&status);
186ecaf4d3fSMark Brown 		if (pid == -1) {
187ecaf4d3fSMark Brown 			perror("wait");
188ecaf4d3fSMark Brown 			goto error;
189ecaf4d3fSMark Brown 		}
190ecaf4d3fSMark Brown 
191ecaf4d3fSMark Brown 		/*
192ecaf4d3fSMark Brown 		 * This should never happen but it's hard to flag in
193ecaf4d3fSMark Brown 		 * the framework.
194ecaf4d3fSMark Brown 		 */
195ecaf4d3fSMark Brown 		if (pid != child)
196ecaf4d3fSMark Brown 			continue;
197ecaf4d3fSMark Brown 
198ecaf4d3fSMark Brown 		if (WIFEXITED(status) || WIFSIGNALED(status))
199ecaf4d3fSMark Brown 			ksft_exit_fail_msg("Child died unexpectedly\n");
200ecaf4d3fSMark Brown 
201ecaf4d3fSMark Brown 		if (!WIFSTOPPED(status))
202ecaf4d3fSMark Brown 			goto error;
203ecaf4d3fSMark Brown 
204ecaf4d3fSMark Brown 		sig = WSTOPSIG(status);
205ecaf4d3fSMark Brown 
206ecaf4d3fSMark Brown 		if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
207ecaf4d3fSMark Brown 			if (errno == ESRCH)
208ecaf4d3fSMark Brown 				goto disappeared;
209ecaf4d3fSMark Brown 
210ecaf4d3fSMark Brown 			if (errno == EINVAL) {
211ecaf4d3fSMark Brown 				sig = 0; /* bust group-stop */
212ecaf4d3fSMark Brown 				goto cont;
213ecaf4d3fSMark Brown 			}
214ecaf4d3fSMark Brown 
215ecaf4d3fSMark Brown 			ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n",
216ecaf4d3fSMark Brown 					      strerror(errno));
217ecaf4d3fSMark Brown 			goto error;
218ecaf4d3fSMark Brown 		}
219ecaf4d3fSMark Brown 
220ecaf4d3fSMark Brown 		if (sig == SIGSTOP && si.si_code == SI_TKILL &&
221ecaf4d3fSMark Brown 		    si.si_pid == pid)
222ecaf4d3fSMark Brown 			break;
223ecaf4d3fSMark Brown 
224ecaf4d3fSMark Brown 	cont:
225ecaf4d3fSMark Brown 		if (ptrace(PTRACE_CONT, pid, NULL, sig)) {
226ecaf4d3fSMark Brown 			if (errno == ESRCH)
227ecaf4d3fSMark Brown 				goto disappeared;
228ecaf4d3fSMark Brown 
229ecaf4d3fSMark Brown 			ksft_test_result_fail("PTRACE_CONT: %s\n",
230ecaf4d3fSMark Brown 					      strerror(errno));
231ecaf4d3fSMark Brown 			goto error;
232ecaf4d3fSMark Brown 		}
233ecaf4d3fSMark Brown 	}
234ecaf4d3fSMark Brown 
235ecaf4d3fSMark Brown 	ksft_print_msg("Parent is %d, child is %d\n", getpid(), child);
236ecaf4d3fSMark Brown 
237ecaf4d3fSMark Brown 	test_tpidr(child);
238cb5aa637SMark Brown 	test_hw_debug(child, NT_ARM_HW_WATCH, "NT_ARM_HW_WATCH");
239cb5aa637SMark Brown 	test_hw_debug(child, NT_ARM_HW_BREAK, "NT_ARM_HW_BREAK");
240ecaf4d3fSMark Brown 
241ecaf4d3fSMark Brown 	ret = EXIT_SUCCESS;
242ecaf4d3fSMark Brown 
243ecaf4d3fSMark Brown error:
244ecaf4d3fSMark Brown 	kill(child, SIGKILL);
245ecaf4d3fSMark Brown 
246ecaf4d3fSMark Brown disappeared:
247ecaf4d3fSMark Brown 	return ret;
248ecaf4d3fSMark Brown }
249ecaf4d3fSMark Brown 
main(void)250ecaf4d3fSMark Brown int main(void)
251ecaf4d3fSMark Brown {
252ecaf4d3fSMark Brown 	int ret = EXIT_SUCCESS;
253ecaf4d3fSMark Brown 	pid_t child;
254ecaf4d3fSMark Brown 
255ecaf4d3fSMark Brown 	srandom(getpid());
256ecaf4d3fSMark Brown 
257ecaf4d3fSMark Brown 	ksft_print_header();
258ecaf4d3fSMark Brown 
259ecaf4d3fSMark Brown 	ksft_set_plan(EXPECTED_TESTS);
260ecaf4d3fSMark Brown 
261ecaf4d3fSMark Brown 	child = fork();
262ecaf4d3fSMark Brown 	if (!child)
263ecaf4d3fSMark Brown 		return do_child();
264ecaf4d3fSMark Brown 
265ecaf4d3fSMark Brown 	if (do_parent(child))
266ecaf4d3fSMark Brown 		ret = EXIT_FAILURE;
267ecaf4d3fSMark Brown 
268ecaf4d3fSMark Brown 	ksft_print_cnts();
269ecaf4d3fSMark Brown 
270ecaf4d3fSMark Brown 	return ret;
271ecaf4d3fSMark Brown }
272