1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2023 Oxide Computer Company
14  */
15 
16 /*
17  * Verify that we can read the xregs of a thread and write them back intact.
18  * This uses libproc as a wrapper. We then start the thread running again and
19  * attempt to write to /proc ourselves to expect an EBUSY because the thread is
20  * not stopped.
21  */
22 
23 #include <libproc.h>
24 #include <thread.h>
25 #include <stdlib.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <sys/sysmacros.h>
30 
31 #include "xsave_util.h"
32 
33 int
34 main(void)
35 {
36 	int ret, fd;
37 	ssize_t sret;
38 	struct ps_prochandle *P;
39 	struct ps_lwphandle *L;
40 	thread_t targ;
41 	prxregset_t *prx, *prx_alt;
42 	size_t prx_len, prx_alt_len;
43 	struct iovec iov[2];
44 	long cmd = PCSXREG;
45 
46 	P = Pgrab(getpid(), PGRAB_RDONLY, &ret);
47 	if (P == NULL) {
48 		errx(EXIT_FAILURE, "failed to grab ourself: %s",
49 		    Pgrab_error(ret));
50 	}
51 
52 	ret = thr_create(NULL, 0, xsu_sleeper_thread, NULL, THR_DETACHED,
53 	    &targ);
54 	if (ret != 0) {
55 		errc(EXIT_FAILURE, ret, "failed to create sleeper thread");
56 	}
57 
58 	L = Lgrab(P, targ, &ret);
59 	if (L == NULL) {
60 		errx(EXIT_FAILURE, "failed to grab our sleeper thread: %s",
61 		    Lgrab_error(ret));
62 	}
63 
64 	ret = Lstop(L, 0);
65 	if (ret != 0) {
66 		err(EXIT_FAILURE, "failed to stop the sleeper thread");
67 	}
68 
69 	if (Lgetxregs(L, &prx, &prx_len) != 0) {
70 		err(EXIT_FAILURE, "failed to get xregs");
71 	}
72 
73 	(void) printf("TEST PASSED: successfully got initial xregs\n");
74 
75 	if (Lsetxregs(L, prx, prx_len) != 0) {
76 		err(EXIT_FAILURE, "failed to set xregs");
77 	}
78 
79 	(void) printf("TEST PASSED: successfully set xregs\n");
80 
81 	if (Lgetxregs(L, &prx_alt, &prx_alt_len) != 0) {
82 		err(EXIT_FAILURE, "failed to get xregs after write");
83 	}
84 
85 	if (prx_len != prx_alt_len) {
86 		errx(EXIT_FAILURE, "xreg length changed across a write: "
87 		    "originally found %zu, now %zu", prx_len, prx_alt_len);
88 	}
89 
90 	if (memcmp(prx, prx_alt, prx_len) != 0) {
91 		const uint8_t *a = (uint8_t *)prx;
92 		const uint8_t *b = (uint8_t *)prx_alt;
93 		for (size_t i = 0; i < prx_len; i++) {
94 			if (a[i] != b[i]) {
95 				(void) fprintf(stderr, "prx[0x%x] = 0x%02x, "
96 				    "prx_alt[0x%x] = 0x%02x\n", i, a[i], i,
97 				    b[i]);
98 			}
99 		}
100 		errx(EXIT_FAILURE, "xregs were not the same!");
101 	}
102 
103 	(void) printf("TEST PASSED: round-trip xregs\n");
104 
105 	if (Lsetrun(L, 0, 0) != 0) {
106 		err(EXIT_FAILURE, "failed to start sleeper thread");
107 	}
108 
109 	/*
110 	 * We write to /proc directly ourselves as a way to avoid libproc's own
111 	 * checks for the state of the thread.
112 	 */
113 	fd = Lctlfd(L);
114 	if (fd < 0) {
115 		errx(EXIT_FAILURE, "failed to get sleeper thread control d");
116 	}
117 
118 	iov[0].iov_base = (char *)&cmd;
119 	iov[0].iov_len = sizeof (long);
120 	iov[1].iov_base = (char *)prx;
121 	iov[1].iov_len = prx_len;
122 	sret = writev(fd, iov, ARRAY_SIZE(iov));
123 	if (sret != -1) {
124 		errx(EXIT_FAILURE, "writev returned %zd, but expected -1",
125 		    sret);
126 	}
127 
128 	if (errno != EBUSY) {
129 		errx(EXIT_FAILURE, "xregs error was not EBUSY, got %d!", errno);
130 	}
131 
132 	(void) printf("TEST PASSED: got EBUSY with PCSXREG to running "
133 	    "thread\n");
134 	Plwp_freexregs(P, prx, prx_len);
135 	return (EXIT_SUCCESS);
136 }
137