--- old/exception_lists/copyright	Wed Apr 12 20:33:17 2023
+++ new/exception_lists/copyright	Wed Apr 12 20:33:17 2023
@@ -414,6 +414,7 @@
 usr/src/test/crypto-tests/tests/digest/data/README
 usr/src/test/libc-tests/tests/err/data/*
 usr/src/test/libmlrpc-tests/tests/netrlogon/krb5_pac_tests/krb5_pac.bin
+usr/src/test/os-tests/tests/xsave/data/*
 usr/src/test/util-tests/tests/dis/*/*.out
 usr/src/test/util-tests/tests/libsff/*.out
 usr/src/test/util-tests/tests/grep_xpg4/files/gout*
--- old/manifest	Wed Apr 12 20:33:17 2023
+++ new/manifest	Wed Apr 12 20:33:17 2023
@@ -4786,6 +4786,7 @@
 f usr/include/sys/stat_impl.h 0644 root bin
 f usr/include/sys/statfs.h 0644 root bin
 f usr/include/sys/statvfs.h 0644 root bin
+f usr/include/sys/stdalign.h 0644 root bin
 f usr/include/sys/stdbool.h 0644 root bin
 f usr/include/sys/stddef.h 0644 root bin
 f usr/include/sys/stdint.h 0644 root bin
@@ -12499,6 +12500,7 @@
 s usr/share/man/man2/getaudit_addr.2=getaudit.2
 f usr/share/man/man2/getauid.2 0444 root bin
 f usr/share/man/man2/getcontext.2 0444 root bin
+s usr/share/man/man2/getcontext_extd.2=getcontext.2
 f usr/share/man/man2/getdents.2 0444 root bin
 s usr/share/man/man2/getegid.2=getuid.2
 s usr/share/man/man2/geteuid.2=getuid.2
@@ -13394,6 +13396,7 @@
 f usr/share/man/man3c/madvise.3c 0444 root bin
 s usr/share/man/man3c/major.3c=makedev.3c
 f usr/share/man/man3c/makecontext.3c 0444 root bin
+s usr/share/man/man3c/makecontext_extd.3c=makecontext.3c
 f usr/share/man/man3c/makedev.3c 0444 root bin
 f usr/share/man/man3c/malloc.3c 0444 root bin
 f usr/share/man/man3c/mblen.3c 0444 root bin
@@ -13917,6 +13920,7 @@
 s usr/share/man/man3c/strxfrm_l.3c=strxfrm.3c
 f usr/share/man/man3c/swab.3c 0444 root bin
 s usr/share/man/man3c/swapcontext.3c=makecontext.3c
+s usr/share/man/man3c/swapcontext_extd.3c=makecontext.3c
 s usr/share/man/man3c/swprintf.3c=fwprintf.3c
 s usr/share/man/man3c/swscanf.3c=fwscanf.3c
 f usr/share/man/man3c/sync_instruction_memory.3c 0444 root bin
@@ -14014,6 +14018,8 @@
 f usr/share/man/man3c/u8_textprep_str.3c 0444 root bin
 f usr/share/man/man3c/u8_validate.3c 0444 root bin
 f usr/share/man/man3c/ualarm.3c 0444 root bin
+f usr/share/man/man3c/ucontext_alloc.3c 0444 root bin
+s usr/share/man/man3c/ucontext_free.3c=ucontext_alloc.3c
 f usr/share/man/man3c/uconv_u16tou32.3c 0444 root bin
 s usr/share/man/man3c/uconv_u16tou8.3c=uconv_u16tou32.3c
 s usr/share/man/man3c/uconv_u32tou16.3c=uconv_u16tou32.3c
@@ -17189,6 +17195,9 @@
 s usr/share/man/man3proc/Ldstop.3proc=Pstopstatus.3proc
 f usr/share/man/man3proc/Lfree.3proc 0444 root bin
 s usr/share/man/man3proc/Lgetareg.3proc=Pgetareg.3proc
+s usr/share/man/man3proc/Lgetfpregs.3proc=Plwp_getregs.3proc
+s usr/share/man/man3proc/Lgetregs.3proc=Plwp_getregs.3proc
+s usr/share/man/man3proc/Lgetxregs.3proc=Plwp_getregs.3proc
 f usr/share/man/man3proc/Lgrab.3proc 0444 root bin
 f usr/share/man/man3proc/Lgrab_error.3proc 0444 root bin
 s usr/share/man/man3proc/Lmain_stack.3proc=Plwp_stack.3proc
@@ -17195,7 +17204,10 @@
 f usr/share/man/man3proc/Lprochandle.3proc 0444 root bin
 f usr/share/man/man3proc/Lpsinfo.3proc 0444 root bin
 s usr/share/man/man3proc/Lputareg.3proc=Pgetareg.3proc
+s usr/share/man/man3proc/Lsetfpregs.3proc=Plwp_getregs.3proc
+s usr/share/man/man3proc/Lsetregs.3proc=Plwp_getregs.3proc
 s usr/share/man/man3proc/Lsetrun.3proc=Psetrun.3proc
+s usr/share/man/man3proc/Lsetxregs.3proc=Plwp_getregs.3proc
 s usr/share/man/man3proc/Lstack.3proc=Plwp_stack.3proc
 f usr/share/man/man3proc/Lstate.3proc 0444 root bin
 f usr/share/man/man3proc/Lstatus.3proc 0444 root bin
@@ -17250,6 +17262,7 @@
 f usr/share/man/man3proc/Plookup_by_addr.3proc 0444 root bin
 s usr/share/man/man3proc/Plookup_by_name.3proc=Plookup_by_addr.3proc
 s usr/share/man/man3proc/Plwp_alt_stack.3proc=Plwp_stack.3proc
+s usr/share/man/man3proc/Plwp_freexregs.3proc=Plwp_getxregs.3proc
 f usr/share/man/man3proc/Plwp_getasrs.3proc 0444 root bin
 s usr/share/man/man3proc/Plwp_getfpregs.3proc=Plwp_getregs.3proc
 f usr/share/man/man3proc/Plwp_getgwindows.3proc 0444 root bin
--- old/usr/src/cmd/mdb/common/mdb/mdb_proc.c	Wed Apr 12 20:33:18 2023
+++ new/usr/src/cmd/mdb/common/mdb/mdb_proc.c	Wed Apr 12 20:33:18 2023
@@ -26,7 +26,7 @@
 /*
  * Copyright 2018 Joyent, Inc.
  * Copyright (c) 2014 by Delphix. All rights reserved.
- * Copyright 2020 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*
@@ -4639,31 +4639,6 @@
 			else if (rd_flags & MDB_TGT_R_8L)
 				r &= 0xffULL;
 
-#if defined(__sparc) && defined(_ILP32)
-			/*
-			 * If we are debugging on 32-bit SPARC, the globals and
-			 * outs can have 32 upper bits stored in the xregs.
-			 */
-			int is_g = (rd_num == R_G0 ||
-			    rd_num >= R_G1 && rd_num <= R_G7);
-			int is_o = (rd_num >= R_O0 && rd_num <= R_O7);
-			prxregset_t xrs;
-
-			if ((is_g || is_o) && PTL_GETXREGS(t, tid, &xrs) == 0 &&
-			    xrs.pr_type == XR_TYPE_V8P) {
-				if (is_g) {
-					xrs.pr_un.pr_v8p.pr_xg[rd_num -
-					    R_G0 + XR_G0] = (uint32_t)(r >> 32);
-				} else if (is_o) {
-					xrs.pr_un.pr_v8p.pr_xo[rd_num -
-					    R_O0 + XR_O0] = (uint32_t)(r >> 32);
-				}
-
-				if (PTL_SETXREGS(t, tid, &xrs) == -1)
-					return (-1);
-			}
-#endif	/* __sparc && _ILP32 */
-
 			if (PTL_GETREGS(t, tid, grs) == 0) {
 				grs[rd_num] = (prgreg_t)r;
 				return (PTL_SETREGS(t, tid, grs));
@@ -4837,33 +4812,38 @@
 	return (set_errno(EMDB_NOPROC));
 }
 
-#ifdef	__sparc
 
 /*ARGSUSED*/
 static int
-pt_lwp_getxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prxregset_t *xregs)
+pt_lwp_getxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prxregset_t **xregs,
+    size_t *sizep)
 {
 	if (t->t_pshandle != NULL) {
 		return (ptl_err(Plwp_getxregs(t->t_pshandle,
-		    (lwpid_t)tid, xregs)));
+		    (lwpid_t)tid, xregs, sizep)));
 	}
 	return (set_errno(EMDB_NOPROC));
 }
 
-/*ARGSUSED*/
+static void
+pt_lwp_freexregs(mdb_tgt_t *t, void *tap, prxregset_t *xregs, size_t size)
+{
+	if (t->t_pshandle != NULL) {
+		Plwp_freexregs(t->t_pshandle, xregs, size);
+	}
+}
+
 static int
 pt_lwp_setxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid,
-    const prxregset_t *xregs)
+    const prxregset_t *xregs, size_t len)
 {
 	if (t->t_pshandle != NULL) {
 		return (ptl_err(Plwp_setxregs(t->t_pshandle,
-		    (lwpid_t)tid, xregs)));
+		    (lwpid_t)tid, xregs, len)));
 	}
 	return (set_errno(EMDB_NOPROC));
 }
 
-#endif	/* __sparc */
-
 /*ARGSUSED*/
 static int
 pt_lwp_getfpregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid,
@@ -4889,18 +4869,17 @@
 }
 
 static const pt_ptl_ops_t proc_lwp_ops = {
-	(int (*)())(uintptr_t)mdb_tgt_nop,
-	(void (*)())(uintptr_t)mdb_tgt_nop,
-	pt_lwp_tid,
-	pt_lwp_iter,
-	pt_lwp_getregs,
-	pt_lwp_setregs,
-#ifdef __sparc
-	pt_lwp_getxregs,
-	pt_lwp_setxregs,
-#endif
-	pt_lwp_getfpregs,
-	pt_lwp_setfpregs
+	.ptl_ctor = (int (*)())(uintptr_t)mdb_tgt_nop,
+	.ptl_dtor = (void (*)())(uintptr_t)mdb_tgt_nop,
+	.ptl_tid = pt_lwp_tid,
+	.ptl_iter = pt_lwp_iter,
+	.ptl_getregs = pt_lwp_getregs,
+	.ptl_setregs = pt_lwp_setregs,
+	.ptl_getxregs = pt_lwp_getxregs,
+	.ptl_freexregs = pt_lwp_freexregs,
+	.ptl_setxregs = pt_lwp_setxregs,
+	.ptl_getfpregs = pt_lwp_getfpregs,
+	.ptl_setfpregs = pt_lwp_setfpregs
 };
 
 static int
@@ -5024,15 +5003,16 @@
 	return (0);
 }
 
-#ifdef __sparc
-
 static int
-pt_tdb_getxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prxregset_t *xregs)
+pt_tdb_getxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prxregset_t **xregs,
+    size_t *sizep)
 {
 	pt_data_t *pt = t->t_data;
 
 	td_thrhandle_t th;
 	td_err_e err;
+	int xregsize;
+	prxregset_t *pxr;
 
 	if (t->t_pshandle == NULL)
 		return (set_errno(EMDB_NOPROC));
@@ -5040,16 +5020,36 @@
 	if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK)
 		return (set_errno(tdb_to_errno(err)));
 
-	err = pt->p_tdb_ops->td_thr_getxregs(&th, xregs);
-	if (err != TD_OK && err != TD_PARTIALREG)
+	if ((err = pt->p_tdb_ops->td_thr_getxregsize(&th, &xregsize)) != TD_OK)
 		return (set_errno(tdb_to_errno(err)));
 
+	if (xregsize == 0) {
+		return (set_errno(ENODATA));
+	}
+
+	pxr = mdb_alloc(xregsize, UM_SLEEP);
+
+	err = pt->p_tdb_ops->td_thr_getxregs(&th, pxr);
+	if (err != TD_OK && err != TD_PARTIALREG) {
+		mdb_free(pxr, xregsize);
+		return (set_errno(tdb_to_errno(err)));
+	}
+
+	*xregs = pxr;
+	*sizep = xregsize;
 	return (0);
 }
 
+static void
+pt_tdb_freexregs(mdb_tgt_t *t __unused, void *tap __unused, prxregset_t *pxr,
+    size_t size)
+{
+	mdb_free(pxr, size);
+}
+
 static int
 pt_tdb_setxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid,
-    const prxregset_t *xregs)
+    const prxregset_t *xregs, size_t len __unused)
 {
 	pt_data_t *pt = t->t_data;
 
@@ -5069,8 +5069,6 @@
 	return (0);
 }
 
-#endif	/* __sparc */
-
 static int
 pt_tdb_getfpregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid,
     prfpregset_t *fpregs)
@@ -5116,18 +5114,17 @@
 }
 
 static const pt_ptl_ops_t proc_tdb_ops = {
-	pt_tdb_ctor,
-	pt_tdb_dtor,
-	pt_tdb_tid,
-	pt_tdb_iter,
-	pt_tdb_getregs,
-	pt_tdb_setregs,
-#ifdef __sparc
-	pt_tdb_getxregs,
-	pt_tdb_setxregs,
-#endif
-	pt_tdb_getfpregs,
-	pt_tdb_setfpregs
+	.ptl_ctor = pt_tdb_ctor,
+	.ptl_dtor = pt_tdb_dtor,
+	.ptl_tid = pt_tdb_tid,
+	.ptl_iter = pt_tdb_iter,
+	.ptl_getregs = pt_tdb_getregs,
+	.ptl_setregs = pt_tdb_setregs,
+	.ptl_getxregs = pt_tdb_getxregs,
+	.ptl_freexregs = pt_tdb_freexregs,
+	.ptl_setxregs = pt_tdb_setxregs,
+	.ptl_getfpregs = pt_tdb_getfpregs,
+	.ptl_setfpregs = pt_tdb_setfpregs
 };
 
 static ssize_t
--- old/usr/src/cmd/mdb/common/mdb/mdb_proc.h	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/common/mdb/mdb_proc.h	Wed Apr 12 20:33:19 2023
@@ -63,12 +63,11 @@
 	int (*ptl_iter)(mdb_tgt_t *, void *, mdb_addrvec_t *);
 	int (*ptl_getregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, prgregset_t);
 	int (*ptl_setregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, prgregset_t);
-#ifdef	__sparc
 	int (*ptl_getxregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t,
-	    prxregset_t *);
+	    prxregset_t **, size_t *);
+	void (*ptl_freexregs)(mdb_tgt_t *, void *, prxregset_t *, size_t);
 	int (*ptl_setxregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t,
-	    const prxregset_t *);
-#endif
+	    const prxregset_t *, size_t);
 	int (*ptl_getfpregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t,
 	    prfpregset_t *);
 	int (*ptl_setfpregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t,
@@ -98,18 +97,18 @@
 	(((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_setregs((t), \
 	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (gregs)))
 
-#ifdef	__sparc
-
-#define	PTL_GETXREGS(t, tid, xregs) \
+#define	PTL_GETXREGS(t, tid, xregs, size) \
 	(((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_getxregs((t), \
-	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (xregs)))
+	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (xregs), (size)))
 
-#define	PTL_SETXREGS(t, tid, xregs) \
+#define	PTL_FREEXREGS(t, xregs, size) \
+	(((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_freexregs((t), \
+	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (xregs), (size)))
+
+#define	PTL_SETXREGS(t, tid, xregs, size) \
 	(((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_setxregs((t), \
-	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (xregs)))
+	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (xregs), (size)))
 
-#endif	/* __sparc */
-
 #define	PTL_GETFPREGS(t, tid, fpregs) \
 	(((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_getfpregs((t), \
 	((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (fpregs)))
--- old/usr/src/cmd/mdb/common/mdb/mdb_tdb.c	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/common/mdb/mdb_tdb.c	Wed Apr 12 20:33:19 2023
@@ -183,7 +183,11 @@
 	if (t->tdb_ops.td_thr_tlsbase == NULL)
 		t->tdb_ops.td_thr_tlsbase = (td_err_e (*)())tdb_notsup;
 
-#ifdef __sparc
+	t->tdb_ops.td_thr_getxregsize = (td_err_e (*)())
+	    dlsym(hdl, "td_thr_getxregsize");
+	if (t->tdb_ops.td_thr_getxregsize == NULL)
+		t->tdb_ops.td_thr_getxregsize = (td_err_e (*)())tdb_notsup;
+
 	t->tdb_ops.td_thr_getxregs = (td_err_e (*)())
 	    dlsym(hdl, "td_thr_getxregs");
 	if (t->tdb_ops.td_thr_getxregs == NULL)
@@ -193,7 +197,6 @@
 	    dlsym(hdl, "td_thr_setxregs");
 	if (t->tdb_ops.td_thr_setxregs == NULL)
 		t->tdb_ops.td_thr_setxregs = (td_err_e (*)())tdb_notsup;
-#endif	/* __sparc */
 
 	return (&t->tdb_ops);
 }
--- old/usr/src/cmd/mdb/common/mdb/mdb_tdb.h	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/common/mdb/mdb_tdb.h	Wed Apr 12 20:33:19 2023
@@ -53,10 +53,9 @@
 	td_err_e (*td_thr_setfpregs)(const td_thrhandle_t *,
 	    const prfpregset_t *);
 	td_err_e (*td_thr_tlsbase)(const td_thrhandle_t *, ulong_t, psaddr_t *);
-#ifdef __sparc
+	td_err_e (*td_thr_getxregsize)(const td_thrhandle_t *, int *);
 	td_err_e (*td_thr_getxregs)(const td_thrhandle_t *, void *);
 	td_err_e (*td_thr_setxregs)(const td_thrhandle_t *, const void *);
-#endif
 } mdb_tdb_ops_t;
 
 typedef struct mdb_tdb_lib {
--- old/usr/src/cmd/mdb/intel/amd64/mdb/Makefile	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/intel/amd64/mdb/Makefile	Wed Apr 12 20:33:19 2023
@@ -31,6 +31,7 @@
 	mdb_ia32util.c \
 	mdb_x86util.c \
 	mdb_bhyve.c \
+	proc_x86util.c \
 	proc_amd64dep.c
 
 %.o: %.c
--- old/usr/src/cmd/mdb/intel/ia32/mdb/Makefile	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/intel/ia32/mdb/Makefile	Wed Apr 12 20:33:19 2023
@@ -22,11 +22,11 @@
 # Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-#ident	"%Z%%M%	%I%	%E% SMI"
 
 SRCS =	kvm_ia32dep.c \
 	kvm_isadep.c \
 	mdb_ia32util.c \
+	proc_x86util.c \
 	proc_ia32dep.c
 
 %.o: ../../mdb/%.c
--- old/usr/src/cmd/mdb/intel/mdb/proc_amd64dep.c	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/intel/mdb/proc_amd64dep.c	Wed Apr 12 20:33:19 2023
@@ -26,10 +26,11 @@
 /*
  * Copyright (c) 2018, Joyent, Inc.
  * Copyright 2019 Doma Gergő Mihály <doma.gergo.mihaly@gmail.com>
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*
- * User Process Target Intel 32-bit component
+ * User Process Target Intel 64-bit component
  *
  * This file provides the ISA-dependent portion of the user process target.
  * For more details on the implementation refer to mdb_proc.c.
@@ -40,6 +41,7 @@
 #include <mdb/mdb_err.h>
 #include <mdb/mdb_isautil.h>
 #include <mdb/mdb_amd64util.h>
+#include <mdb/proc_x86util.h>
 #include <mdb/mdb.h>
 
 #include <sys/ucontext.h>
@@ -47,6 +49,7 @@
 #include <libproc.h>
 #include <sys/fp.h>
 #include <ieeefp.h>
+#include <sys/sysmacros.h>
 
 #include <stddef.h>
 
@@ -279,242 +282,15 @@
 	return (set_errno(ENOTSUP));
 }
 
-static const char *
-fpcw2str(uint32_t cw, char *buf, size_t nbytes)
-{
-	char *end = buf + nbytes;
-	char *p = buf;
-
-	buf[0] = '\0';
-
-	/*
-	 * Decode all exception masks in the x87 FPU Control Word.
-	 *
-	 * See here:
-	 * Intel® 64 and IA-32 Architectures Software Developer’s Manual,
-	 * Volume 1: Basic Architecture, 8.1.5 x87 FPU Control Word
-	 */
-	if (cw & FPIM)	/* Invalid operation mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|IM");
-	if (cw & FPDM)	/* Denormalized operand mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|DM");
-	if (cw & FPZM)	/* Zero divide mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZM");
-	if (cw & FPOM)	/* Overflow mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|OM");
-	if (cw & FPUM)	/* Underflow mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|UM");
-	if (cw & FPPM)	/* Precision mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|PM");
-
-	/*
-	 * Decode precision control options.
-	 */
-	switch (cw & FPPC) {
-	case FPSIG24:
-		/* 24-bit significand, single precision. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|SIG24");
-		break;
-	case FPSIG53:
-		/* 53-bit significand, double precision. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|SIG53");
-		break;
-	case FPSIG64:
-		/* 64-bit significand, double extended precision. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|SIG64");
-		break;
-	default:
-		/*
-		 * Should never happen.
-		 * Value 0x00000100 is 'Reserved'.
-		 */
-		break;
-	}
-
-	/*
-	 * Decode rounding control options.
-	 */
-	switch (cw & FPRC) {
-	case FPRTN:
-		/* Round to nearest, or to even if equidistant. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTN");
-		break;
-	case FPRD:
-		/* Round down. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RD");
-		break;
-	case FPRU:
-		/* Round up. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RU");
-		break;
-	case FPCHOP:
-		/* Truncate. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTZ");
-		break;
-	default:
-		/*
-		 * This is a two-bit field.
-		 * No other options left.
-		 */
-		break;
-	}
-
-	/*
-	 * Decode infinity control options.
-	 *
-	 * This field has been retained for compatibility with
-	 * the 287 and earlier co-processors.
-	 * In the more modern FPUs, this bit is disregarded and
-	 * both -infinity and +infinity are respected.
-	 * Comment source: SIMPLY FPU by Raymond Filiatreault
-	 */
-	switch (cw & FPIC) {
-	case FPP:
-		/*
-		 * Projective infinity.
-		 * Both -infinity and +infinity are treated as
-		 * unsigned infinity.
-		 */
-		p += mdb_snprintf(p, (size_t)(end - p), "|P");
-		break;
-	case FPA:
-		/*
-		 * Affine infinity.
-		 * Respects both -infinity and +infinity.
-		 */
-		p += mdb_snprintf(p, (size_t)(end - p), "|A");
-		break;
-	default:
-		/*
-		 * This is a one-bit field.
-		 * No other options left.
-		 */
-		break;
-	}
-
-	if (cw & WFPB17)
-		p += mdb_snprintf(p, (size_t)(end - p), "|WFPB17");
-	if (cw & WFPB24)
-		p += mdb_snprintf(p, (size_t)(end - p), "|WFPB24");
-
-	if (buf[0] == '|')
-		return (buf + 1);
-
-	return ("0");
-}
-
-static const char *
-fpsw2str(uint32_t cw, char *buf, size_t nbytes)
-{
-	char *end = buf + nbytes;
-	char *p = buf;
-
-	buf[0] = '\0';
-
-	/*
-	 * Decode all masks in the 80387 status word.
-	 */
-	if (cw & FPS_IE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|IE");
-	if (cw & FPS_DE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DE");
-	if (cw & FPS_ZE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZE");
-	if (cw & FPS_OE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|OE");
-	if (cw & FPS_UE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|UE");
-	if (cw & FPS_PE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|PE");
-	if (cw & FPS_SF)
-		p += mdb_snprintf(p, (size_t)(end - p), "|SF");
-	if (cw & FPS_ES)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ES");
-	if (cw & FPS_C0)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C0");
-	if (cw & FPS_C1)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C1");
-	if (cw & FPS_C2)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C2");
-	if (cw & FPS_C3)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C3");
-	if (cw & FPS_B)
-		p += mdb_snprintf(p, (size_t)(end - p), "|B");
-
-	if (buf[0] == '|')
-		return (buf + 1);
-
-	return ("0");
-}
-
-static const char *
-fpmxcsr2str(uint32_t mxcsr, char *buf, size_t nbytes)
-{
-	char *end = buf + nbytes;
-	char *p = buf;
-
-	buf[0] = '\0';
-
-	/*
-	 * Decode the MXCSR word
-	 */
-	if (mxcsr & SSE_IE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|IE");
-	if (mxcsr & SSE_DE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DE");
-	if (mxcsr & SSE_ZE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZE");
-	if (mxcsr & SSE_OE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|OE");
-	if (mxcsr & SSE_UE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|UE");
-	if (mxcsr & SSE_PE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|PE");
-
-	if (mxcsr & SSE_DAZ)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DAZ");
-
-	if (mxcsr & SSE_IM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|IM");
-	if (mxcsr & SSE_DM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DM");
-	if (mxcsr & SSE_ZM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZM");
-	if (mxcsr & SSE_OM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|OM");
-	if (mxcsr & SSE_UM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|UM");
-	if (mxcsr & SSE_PM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|PM");
-
-	if ((mxcsr & SSE_RC) == (SSE_RD|SSE_RU))
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTZ");
-	else if (mxcsr & SSE_RD)
-		p += mdb_snprintf(p, (size_t)(end - p), "|RD");
-	else if (mxcsr & SSE_RU)
-		p += mdb_snprintf(p, (size_t)(end - p), "|RU");
-	else
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTN");
-
-	if (mxcsr & SSE_FZ)
-		p += mdb_snprintf(p, (size_t)(end - p), "|FZ");
-
-	if (buf[0] == '|')
-		return (buf + 1);
-	return ("0");
-}
-
-/*ARGSUSED*/
 int
 pt_fpregs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
 {
-	mdb_tgt_t *t = mdb.m_target;
-	mdb_tgt_tid_t tid;
+	int ret;
 	prfpregset_t fprs;
 	struct _fpchip_state fps;
 	char buf[256];
 	uint_t top;
-	int i;
+	size_t i;
 
 	/*
 	 * Union for overlaying _fpreg structure on to quad-precision
@@ -526,37 +302,16 @@
 	} fpru;
 
 	/*
-	 * Array of strings corresponding to FPU tag word values (see
-	 * section 7.3.6 of the Intel Programmer's Reference Manual).
+	 * We use common code between 32-bit and 64-bit x86 to capture and print
+	 * the extended vector state. The remaining classic 387 state is
+	 * finicky and different enough that it is left to be dealt with on its
+	 * own.
 	 */
-	const char *tag_strings[] = { "valid", "zero", "special", "empty" };
+	if ((ret = x86_pt_fpregs_common(addr, flags, argc, &fprs)) != DCMD_OK)
+		return (ret);
 
-	if (argc != 0)
-		return (DCMD_USAGE);
-
-	if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_UNDEAD) {
-		mdb_warn("no process active\n");
-		return (DCMD_ERR);
-	}
-
-	if (Pstate(t->t_pshandle) == PS_LOST) {
-		mdb_warn("debugger has lost control of process\n");
-		return (DCMD_ERR);
-	}
-
-	if (flags & DCMD_ADDRSPEC)
-		tid = (mdb_tgt_tid_t)addr;
-	else
-		tid = PTL_TID(t);
-
-	mdb_printf("AMD64 (80486 chip with SSE)\n");
-
-	if (PTL_GETFPREGS(t, tid, &fprs) != 0) {
-		mdb_warn("failed to get floating point registers");
-		return (DCMD_ERR);
-	}
-
 	bcopy(&fprs.fp_reg_set.fpchip_state, &fps, sizeof (fps));
+	mdb_printf("387 and FP Control State\n");
 
 	fps.status &= 0xffff;	/* saved status word is really 16 bits */
 
@@ -574,7 +329,7 @@
 	mdb_printf("rip    0x%x\n", fps.rip);
 	mdb_printf("rdp    0x%x\n\n", fps.rdp);
 
-	for (i = 0; i < 8; i++) {
+	for (i = 0; i < ARRAY_SIZE(fps.st); i++) {
 		/*
 		 * Recall that we need to use the current TOP-of-stack value to
 		 * associate the _st[] index back to a physical register number,
@@ -619,19 +374,11 @@
 		    i, fpru.reg.exponent,
 		    fpru.reg.significand[3], fpru.reg.significand[2],
 		    fpru.reg.significand[1], fpru.reg.significand[0],
-		    fpru.ld, tag_strings[tag_value]);
+		    fpru.ld, fptag2str(tag_value));
 	}
 
-	mdb_printf("\nmxcsr  0x%04x (%s)\n", fps.mxcsr,
-	    fpmxcsr2str(fps.mxcsr, buf, sizeof (buf)));
-	mdb_printf("xcp    0x%04x (%s)\n\n", fps.xstatus,
-	    fpmxcsr2str(fps.xstatus, buf, sizeof (buf)));
+	x86_pt_fpregs_sse_ctl(fps.mxcsr, fps.xstatus, buf, sizeof (buf));
 
-	for (i = 0; i < 8; i++)
-		mdb_printf("%%xmm%d  0x%08x%08x%08x%08x\n", i,
-		    fps.xmm[i]._l[3], fps.xmm[i]._l[2],
-		    fps.xmm[i]._l[1], fps.xmm[i]._l[0]);
-
 	return (DCMD_OK);
 }
 
--- old/usr/src/cmd/mdb/intel/mdb/proc_ia32dep.c	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/intel/mdb/proc_ia32dep.c	Wed Apr 12 20:33:19 2023
@@ -26,6 +26,7 @@
 /*
  * Copyright 2019 Doma Gergő Mihály <doma.gergo.mihaly@gmail.com>
  * Copyright 2018 Joyent, Inc.
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*
@@ -40,6 +41,7 @@
 #include <mdb/mdb_err.h>
 #include <mdb/mdb_isautil.h>
 #include <mdb/mdb_ia32util.h>
+#include <mdb/proc_x86util.h>
 #include <mdb/mdb.h>
 
 #include <sys/ucontext.h>
@@ -47,6 +49,7 @@
 #include <libproc.h>
 #include <sys/fp.h>
 #include <ieeefp.h>
+#include <sys/sysmacros.h>
 
 #include <stddef.h>
 
@@ -220,244 +223,15 @@
 	return (DCMD_OK);
 }
 
-static const char *
-fpcw2str(uint32_t cw, char *buf, size_t nbytes)
-{
-	char *end = buf + nbytes;
-	char *p = buf;
-
-	buf[0] = '\0';
-
-	/*
-	 * Decode all exception masks in the x87 FPU Control Word.
-	 *
-	 * See here:
-	 * Intel® 64 and IA-32 Architectures Software Developer’s Manual,
-	 * Volume 1: Basic Architecture, 8.1.5 x87 FPU Control Word
-	 */
-	if (cw & FPIM)	/* Invalid operation mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|IM");
-	if (cw & FPDM)	/* Denormalized operand mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|DM");
-	if (cw & FPZM)	/* Zero divide mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZM");
-	if (cw & FPOM)	/* Overflow mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|OM");
-	if (cw & FPUM)	/* Underflow mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|UM");
-	if (cw & FPPM)	/* Precision mask. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|PM");
-
-	/*
-	 * Decode precision control options.
-	 */
-	switch (cw & FPPC) {
-	case FPSIG24:
-		/* 24-bit significand, single precision. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|SIG24");
-		break;
-	case FPSIG53:
-		/* 53-bit significand, double precision. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|SIG53");
-		break;
-	case FPSIG64:
-		/* 64-bit significand, double extended precision. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|SIG64");
-		break;
-	default:
-		/*
-		 * Should never happen.
-		 * Value 0x00000100 is 'Reserved'.
-		 */
-		break;
-	}
-
-	/*
-	 * Decode rounding control options.
-	 */
-	switch (cw & FPRC) {
-	case FPRTN:
-		/* Round to nearest, or to even if equidistant. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTN");
-		break;
-	case FPRD:
-		/* Round down. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RD");
-		break;
-	case FPRU:
-		/* Round up. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RU");
-		break;
-	case FPCHOP:
-		/* Truncate. */
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTZ");
-		break;
-	default:
-		/*
-		 * This is a two-bit field.
-		 * No other options left.
-		 */
-		break;
-	}
-
-	/*
-	 * Decode infinity control options.
-	 *
-	 * This field has been retained for compatibility with
-	 * the 287 and earlier co-processors.
-	 * In the more modern FPUs, this bit is disregarded and
-	 * both -infinity and +infinity are respected.
-	 * Comment source: SIMPLY FPU by Raymond Filiatreault
-	 */
-	switch (cw & FPIC) {
-	case FPP:
-		/*
-		 * Projective infinity.
-		 * Both -infinity and +infinity are treated as
-		 * unsigned infinity.
-		 */
-		p += mdb_snprintf(p, (size_t)(end - p), "|P");
-		break;
-	case FPA:
-		/*
-		 * Affine infinity.
-		 * Respects both -infinity and +infinity.
-		 */
-		p += mdb_snprintf(p, (size_t)(end - p), "|A");
-		break;
-	default:
-		/*
-		 * This is a one-bit field.
-		 * No other options left.
-		 */
-		break;
-	}
-
-	if (cw & WFPB17)
-		p += mdb_snprintf(p, (size_t)(end - p), "|WFPB17");
-	if (cw & WFPB24)
-		p += mdb_snprintf(p, (size_t)(end - p), "|WFPB24");
-
-	if (buf[0] == '|')
-		return (buf + 1);
-
-	return ("0");
-}
-
-static const char *
-fpsw2str(uint32_t cw, char *buf, size_t nbytes)
-{
-	char *end = buf + nbytes;
-	char *p = buf;
-
-	buf[0] = '\0';
-
-	/*
-	 * Decode all masks in the 80387 status word.
-	 */
-	if (cw & FPS_IE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|IE");
-	if (cw & FPS_DE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DE");
-	if (cw & FPS_ZE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZE");
-	if (cw & FPS_OE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|OE");
-	if (cw & FPS_UE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|UE");
-	if (cw & FPS_PE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|PE");
-	if (cw & FPS_SF)
-		p += mdb_snprintf(p, (size_t)(end - p), "|SF");
-	if (cw & FPS_ES)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ES");
-	if (cw & FPS_C0)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C0");
-	if (cw & FPS_C1)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C1");
-	if (cw & FPS_C2)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C2");
-	if (cw & FPS_C3)
-		p += mdb_snprintf(p, (size_t)(end - p), "|C3");
-	if (cw & FPS_B)
-		p += mdb_snprintf(p, (size_t)(end - p), "|B");
-
-	if (buf[0] == '|')
-		return (buf + 1);
-
-	return ("0");
-}
-
-static const char *
-fpmxcsr2str(uint32_t mxcsr, char *buf, size_t nbytes)
-{
-	char *end = buf + nbytes;
-	char *p = buf;
-
-	buf[0] = '\0';
-
-	/*
-	 * Decode the MXCSR word
-	 */
-	if (mxcsr & SSE_IE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|IE");
-	if (mxcsr & SSE_DE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DE");
-	if (mxcsr & SSE_ZE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZE");
-	if (mxcsr & SSE_OE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|OE");
-	if (mxcsr & SSE_UE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|UE");
-	if (mxcsr & SSE_PE)
-		p += mdb_snprintf(p, (size_t)(end - p), "|PE");
-
-	if (mxcsr & SSE_DAZ)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DAZ");
-
-	if (mxcsr & SSE_IM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|IM");
-	if (mxcsr & SSE_DM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|DM");
-	if (mxcsr & SSE_ZM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|ZM");
-	if (mxcsr & SSE_OM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|OM");
-	if (mxcsr & SSE_UM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|UM");
-	if (mxcsr & SSE_PM)
-		p += mdb_snprintf(p, (size_t)(end - p), "|PM");
-
-	if ((mxcsr & SSE_RC) == (SSE_RD|SSE_RU))
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTZ");
-	else if (mxcsr & SSE_RD)
-		p += mdb_snprintf(p, (size_t)(end - p), "|RD");
-	else if (mxcsr & SSE_RU)
-		p += mdb_snprintf(p, (size_t)(end - p), "|RU");
-	else
-		p += mdb_snprintf(p, (size_t)(end - p), "|RTN");
-
-	if (mxcsr & SSE_FZ)
-		p += mdb_snprintf(p, (size_t)(end - p), "|FZ");
-
-	if (buf[0] == '|')
-		return (buf + 1);
-	return ("0");
-}
-
-/*ARGSUSED*/
 int
 pt_fpregs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
 {
-	mdb_tgt_t *t = mdb.m_target;
-	mdb_tgt_tid_t tid;
-	uint32_t hw = FP_NO;
-	uint_t sse = 0;
+	int ret;
 	prfpregset_t fprs;
 	struct _fpstate fps;
 	char buf[256];
 	uint_t top;
-	int i;
+	size_t i;
 
 	/*
 	 * Union for overlaying _fpreg structure on to quad-precision
@@ -469,72 +243,16 @@
 	} fpru;
 
 	/*
-	 * Array of strings corresponding to FPU tag word values (see
-	 * section 7.3.6 of the Intel Programmer's Reference Manual).
+	 * We use common code between 32-bit and 64-bit x86 to capture and print
+	 * the extended vector state. The remaining classic 387 state is
+	 * finicky and different enough that it is left to be dealt with on its
+	 * own.
 	 */
-	const char *tag_strings[] = { "valid", "zero", "special", "empty" };
+	if ((ret = x86_pt_fpregs_common(addr, flags, argc, &fprs)) != DCMD_OK)
+		return (ret);
 
-	if (argc != 0)
-		return (DCMD_USAGE);
-
-	if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_UNDEAD) {
-		mdb_warn("no process active\n");
-		return (DCMD_ERR);
-	}
-
-	if (Pstate(t->t_pshandle) == PS_LOST) {
-		mdb_warn("debugger has lost control of process\n");
-		return (DCMD_ERR);
-	}
-
-	if (flags & DCMD_ADDRSPEC)
-		tid = (mdb_tgt_tid_t)addr;
-	else
-		tid = PTL_TID(t);
-
-	if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &hw,
-	    sizeof (hw), "libc.so", "_fp_hw") < 0 &&
-	    mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &hw,
-	    sizeof (hw), MDB_TGT_OBJ_EXEC, "_fp_hw") < 0)
-		mdb_warn("failed to read _fp_hw value");
-
-	if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &sse,
-	    sizeof (sse), "libc.so", "_sse_hw") < 0 &&
-	    mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &sse,
-	    sizeof (sse), MDB_TGT_OBJ_EXEC, "_sse_hw") < 0)
-		mdb_warn("failed to read _sse_hw value");
-
-	mdb_printf("_fp_hw 0x%02x (", hw);
-	switch (hw) {
-	case FP_SW:
-		mdb_printf("80387 software emulator");
-		break;
-	case FP_287:
-		mdb_printf("80287 chip");
-		break;
-	case FP_387:
-		mdb_printf("80387 chip");
-		break;
-	case FP_486:
-		mdb_printf("80486 chip");
-		break;
-	default:
-		mdb_printf("no floating point support");
-		break;
-	}
-	if (sse)
-		mdb_printf(" with SSE");
-	mdb_printf(")\n");
-
-	if (!(hw & FP_HW))
-		return (DCMD_OK); /* just abort if no hardware present */
-
-	if (PTL_GETFPREGS(t, tid, &fprs) != 0) {
-		mdb_warn("failed to get floating point registers");
-		return (DCMD_ERR);
-	}
-
 	bcopy(&fprs.fp_reg_set.fpchip_state, &fps, sizeof (fps));
+	mdb_printf("387 and FP Control State\n");
 
 	fps.cw &= 0xffff;	/* control word is really 16 bits */
 	fps.sw &= 0xffff;	/* status word is really 16 bits */
@@ -557,7 +275,7 @@
 	mdb_printf("dtoff  %a\n", fps.dataoff);
 	mdb_printf("dtsel  0x%x\n\n", fps.datasel);
 
-	for (i = 0; i < 8; i++) {
+	for (i = 0; i < ARRAY_SIZE(fps._st); i++) {
 		/*
 		 * Recall that we need to use the current TOP-of-stack value to
 		 * associate the _st[] index back to a physical register number,
@@ -573,22 +291,11 @@
 		    i, fpru.reg.exponent,
 		    fpru.reg.significand[3], fpru.reg.significand[2],
 		    fpru.reg.significand[1], fpru.reg.significand[0],
-		    fpru.ld, tag_strings[tag_value]);
+		    fpru.ld, fptag2str(tag_value));
 	}
 
-	if (!sse)
-		return (DCMD_OK);
+	x86_pt_fpregs_sse_ctl(fps.mxcsr, fps.xstatus, buf, sizeof (buf));
 
-	mdb_printf("\nmxcsr  0x%04x (%s)\n", fps.mxcsr,
-	    fpmxcsr2str(fps.mxcsr, buf, sizeof (buf)));
-	mdb_printf("xcp    0x%04x (%s)\n\n", fps.xstatus,
-	    fpmxcsr2str(fps.xstatus, buf, sizeof (buf)));
-
-	for (i = 0; i < 8; i++)
-		mdb_printf("%%xmm%d  0x%08x%08x%08x%08x\n", i,
-		    fps.xmm[i][3], fps.xmm[i][2],
-		    fps.xmm[i][1], fps.xmm[i][0]);
-
 	return (DCMD_OK);
 }
 
--- /dev/null	Wed Apr 12 20:33:19 2023
+++ new/usr/src/cmd/mdb/intel/mdb/proc_x86util.c	Wed Apr 12 20:33:19 2023
@@ -0,0 +1,695 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+/*
+ * Copyright 2019 Doma Gergő Mihály <doma.gergo.mihaly@gmail.com>
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * Consolidated routines that are shared between the 32-bit and 64-bit x86 mdb
+ * proc targets.
+ */
+
+#include <mdb/mdb_proc.h>
+#include <mdb/mdb_err.h>
+#include <mdb/proc_x86util.h>
+#include <mdb/mdb.h>
+
+#include <libproc.h>
+#include <sys/fp.h>
+#include <ieeefp.h>
+#include <sys/sysmacros.h>
+
+const char *
+fpcw2str(uint32_t cw, char *buf, size_t nbytes)
+{
+	char *end = buf + nbytes;
+	char *p = buf;
+
+	buf[0] = '\0';
+
+	/*
+	 * Decode all exception masks in the x87 FPU Control Word.
+	 *
+	 * See here:
+	 * Intel® 64 and IA-32 Architectures Software Developer’s Manual,
+	 * Volume 1: Basic Architecture, 8.1.5 x87 FPU Control Word
+	 */
+	if (cw & FPIM)	/* Invalid operation mask. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|IM");
+	if (cw & FPDM)	/* Denormalized operand mask. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|DM");
+	if (cw & FPZM)	/* Zero divide mask. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|ZM");
+	if (cw & FPOM)	/* Overflow mask. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|OM");
+	if (cw & FPUM)	/* Underflow mask. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|UM");
+	if (cw & FPPM)	/* Precision mask. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|PM");
+
+	/*
+	 * Decode precision control options.
+	 */
+	switch (cw & FPPC) {
+	case FPSIG24:
+		/* 24-bit significand, single precision. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|SIG24");
+		break;
+	case FPSIG53:
+		/* 53-bit significand, double precision. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|SIG53");
+		break;
+	case FPSIG64:
+		/* 64-bit significand, double extended precision. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|SIG64");
+		break;
+	default:
+		/*
+		 * Should never happen.
+		 * Value 0x00000100 is 'Reserved'.
+		 */
+		break;
+	}
+
+	/*
+	 * Decode rounding control options.
+	 */
+	switch (cw & FPRC) {
+	case FPRTN:
+		/* Round to nearest, or to even if equidistant. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|RTN");
+		break;
+	case FPRD:
+		/* Round down. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|RD");
+		break;
+	case FPRU:
+		/* Round up. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|RU");
+		break;
+	case FPCHOP:
+		/* Truncate. */
+		p += mdb_snprintf(p, (size_t)(end - p), "|RTZ");
+		break;
+	default:
+		/*
+		 * This is a two-bit field.
+		 * No other options left.
+		 */
+		break;
+	}
+
+	/*
+	 * Decode infinity control options.
+	 *
+	 * This field has been retained for compatibility with
+	 * the 287 and earlier co-processors.
+	 * In the more modern FPUs, this bit is disregarded and
+	 * both -infinity and +infinity are respected.
+	 * Comment source: SIMPLY FPU by Raymond Filiatreault
+	 */
+	switch (cw & FPIC) {
+	case FPP:
+		/*
+		 * Projective infinity.
+		 * Both -infinity and +infinity are treated as
+		 * unsigned infinity.
+		 */
+		p += mdb_snprintf(p, (size_t)(end - p), "|P");
+		break;
+	case FPA:
+		/*
+		 * Affine infinity.
+		 * Respects both -infinity and +infinity.
+		 */
+		p += mdb_snprintf(p, (size_t)(end - p), "|A");
+		break;
+	default:
+		/*
+		 * This is a one-bit field.
+		 * No other options left.
+		 */
+		break;
+	}
+
+	if (cw & WFPB17)
+		p += mdb_snprintf(p, (size_t)(end - p), "|WFPB17");
+	if (cw & WFPB24)
+		p += mdb_snprintf(p, (size_t)(end - p), "|WFPB24");
+
+	if (buf[0] == '|')
+		return (buf + 1);
+
+	return ("0");
+}
+
+const char *
+fpsw2str(uint32_t cw, char *buf, size_t nbytes)
+{
+	char *end = buf + nbytes;
+	char *p = buf;
+
+	buf[0] = '\0';
+
+	/*
+	 * Decode all masks in the 80387 status word.
+	 */
+	if (cw & FPS_IE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|IE");
+	if (cw & FPS_DE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|DE");
+	if (cw & FPS_ZE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|ZE");
+	if (cw & FPS_OE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|OE");
+	if (cw & FPS_UE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|UE");
+	if (cw & FPS_PE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|PE");
+	if (cw & FPS_SF)
+		p += mdb_snprintf(p, (size_t)(end - p), "|SF");
+	if (cw & FPS_ES)
+		p += mdb_snprintf(p, (size_t)(end - p), "|ES");
+	if (cw & FPS_C0)
+		p += mdb_snprintf(p, (size_t)(end - p), "|C0");
+	if (cw & FPS_C1)
+		p += mdb_snprintf(p, (size_t)(end - p), "|C1");
+	if (cw & FPS_C2)
+		p += mdb_snprintf(p, (size_t)(end - p), "|C2");
+	if (cw & FPS_C3)
+		p += mdb_snprintf(p, (size_t)(end - p), "|C3");
+	if (cw & FPS_B)
+		p += mdb_snprintf(p, (size_t)(end - p), "|B");
+
+	if (buf[0] == '|')
+		return (buf + 1);
+
+	return ("0");
+}
+
+const char *
+fpmxcsr2str(uint32_t mxcsr, char *buf, size_t nbytes)
+{
+	char *end = buf + nbytes;
+	char *p = buf;
+
+	buf[0] = '\0';
+
+	/*
+	 * Decode the MXCSR word
+	 */
+	if (mxcsr & SSE_IE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|IE");
+	if (mxcsr & SSE_DE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|DE");
+	if (mxcsr & SSE_ZE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|ZE");
+	if (mxcsr & SSE_OE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|OE");
+	if (mxcsr & SSE_UE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|UE");
+	if (mxcsr & SSE_PE)
+		p += mdb_snprintf(p, (size_t)(end - p), "|PE");
+
+	if (mxcsr & SSE_DAZ)
+		p += mdb_snprintf(p, (size_t)(end - p), "|DAZ");
+
+	if (mxcsr & SSE_IM)
+		p += mdb_snprintf(p, (size_t)(end - p), "|IM");
+	if (mxcsr & SSE_DM)
+		p += mdb_snprintf(p, (size_t)(end - p), "|DM");
+	if (mxcsr & SSE_ZM)
+		p += mdb_snprintf(p, (size_t)(end - p), "|ZM");
+	if (mxcsr & SSE_OM)
+		p += mdb_snprintf(p, (size_t)(end - p), "|OM");
+	if (mxcsr & SSE_UM)
+		p += mdb_snprintf(p, (size_t)(end - p), "|UM");
+	if (mxcsr & SSE_PM)
+		p += mdb_snprintf(p, (size_t)(end - p), "|PM");
+
+	if ((mxcsr & SSE_RC) == (SSE_RD|SSE_RU))
+		p += mdb_snprintf(p, (size_t)(end - p), "|RTZ");
+	else if (mxcsr & SSE_RD)
+		p += mdb_snprintf(p, (size_t)(end - p), "|RD");
+	else if (mxcsr & SSE_RU)
+		p += mdb_snprintf(p, (size_t)(end - p), "|RU");
+	else
+		p += mdb_snprintf(p, (size_t)(end - p), "|RTN");
+
+	if (mxcsr & SSE_FZ)
+		p += mdb_snprintf(p, (size_t)(end - p), "|FZ");
+
+	if (buf[0] == '|')
+		return (buf + 1);
+	return ("0");
+}
+
+const char *
+fptag2str(uint32_t val)
+{
+	/*
+	 * Array of strings corresponding to FPU tag word values (see
+	 * section 7.3.6 of the Intel Programmer's Reference Manual).
+	 */
+	const char *tag_strings[] = { "valid", "zero", "special", "empty" };
+
+	if (val >= ARRAY_SIZE(tag_strings)) {
+		return ("unknown");
+	}
+
+	return (tag_strings[val]);
+}
+
+static uintptr_t
+xregs_data_ptr(const prxregset_hdr_t *prx, const prxregset_info_t *info)
+{
+	uintptr_t base = (uintptr_t)prx;
+	return (base + info->pri_offset);
+}
+
+static boolean_t
+xregs_valid_data(const prxregset_hdr_t *prx, const prxregset_info_t *info,
+    size_t exp_size, const char *type)
+{
+	size_t last_byte;
+
+	if (info->pri_size != exp_size) {
+		mdb_warn("%s has unexpeced size 0x%lx, expected 0x%lx -- "
+		    "cannot use\n", type, info->pri_size, exp_size);
+		return (B_FALSE);
+	}
+
+	last_byte = (size_t)info->pri_size + (size_t)info->pri_offset;
+	if (last_byte < MIN(info->pri_size, info->pri_offset)) {
+		mdb_warn("%s size 0x%lx and offset 0x%lx appear to overflow -- "
+		    "canot use\n", type, info->pri_size, info->pri_offset);
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+static const char *
+fp_type_to_str(x86_vector_type_t type)
+{
+	switch (type) {
+	case XMM:
+		return ("128-bit %xmm");
+	case YMM:
+		return ("256-bit %ymm");
+	case ZMM:
+		return ("512-bit %zmm");
+	default:
+		return ("unknown");
+	}
+}
+
+/*
+ * Go through the xregs data that we have and make sure that it makes sense for
+ * printing. In particular we need to make sure:
+ *
+ *  o The structure type is what we expect
+ *  o That its overall size is correct
+ *  o That we can find the expected set of data pointers that should be here
+ *  o That the information pointers actually make sense and their contents are
+ *    both the correct size and within the overall structure. Note, we do not
+ *    check for overlapping data regions right now, meaning that some weird
+ *    notes may still lead to weird data.
+ */
+static boolean_t
+pt_xregs_process(const prxregset_hdr_t *prx, size_t found_size,
+    x86_xregs_info_t *xinfo)
+{
+	bzero(xinfo, sizeof (*xinfo));
+
+	if (prx->pr_type != PR_TYPE_XSAVE) {
+		mdb_warn("prxregset has unknown type: 0x%x\n -- falling back "
+		    "to fpregset_t\n", prx->pr_type);
+		return (B_FALSE);
+	}
+
+	if (prx->pr_size < found_size) {
+		mdb_warn("prxregset has greater size than we were given: "
+		    "found 0x%lx, have 0x%lx\n", prx->pr_size, found_size);
+		return (B_FALSE);
+	}
+
+	for (uint32_t i = 0; i < prx->pr_ninfo; i++) {
+		switch (prx->pr_info[i].pri_type) {
+		case PRX_INFO_XCR:
+			if (xregs_valid_data(prx, &prx->pr_info[i],
+			    sizeof (prxregset_xcr_t), "xcr")) {
+				xinfo->xri_xcr = (void *)xregs_data_ptr(prx,
+				    &prx->pr_info[i]);
+			}
+			break;
+		case PRX_INFO_XSAVE:
+			if (xregs_valid_data(prx, &prx->pr_info[i],
+			    sizeof (prxregset_xsave_t), "xsave")) {
+				xinfo->xri_xsave = (void *)xregs_data_ptr(prx,
+				    &prx->pr_info[i]);
+			}
+			break;
+		case PRX_INFO_YMM:
+			if (xregs_valid_data(prx, &prx->pr_info[i],
+			    sizeof (prxregset_ymm_t), "ymm")) {
+				xinfo->xri_ymm = (void *)xregs_data_ptr(prx,
+				    &prx->pr_info[i]);
+			}
+			break;
+		case PRX_INFO_OPMASK:
+			if (xregs_valid_data(prx, &prx->pr_info[i],
+			    sizeof (prxregset_opmask_t), "opmask")) {
+				xinfo->xri_opmask = (void *)xregs_data_ptr(prx,
+				    &prx->pr_info[i]);
+			}
+			break;
+		case PRX_INFO_ZMM:
+			if (xregs_valid_data(prx, &prx->pr_info[i],
+			    sizeof (prxregset_zmm_t), "zmm")) {
+				xinfo->xri_zmm = (void *)xregs_data_ptr(prx,
+				    &prx->pr_info[i]);
+			}
+			break;
+		case PRX_INFO_HI_ZMM:
+			if (xregs_valid_data(prx, &prx->pr_info[i],
+			    sizeof (prxregset_hi_zmm_t), "hi_zmm")) {
+				xinfo->xri_hi_zmm = (void *)xregs_data_ptr(prx,
+				    &prx->pr_info[i]);
+			}
+			break;
+		default:
+			mdb_warn("ignoring unexpected xreg info type: 0x%x\n",
+			    prx->pr_info[i].pri_type);
+			break;
+		}
+	}
+
+	/*
+	 * Now that we have gotten this far, we go and figure out what the
+	 * largest type of information we actually have is. We check from the
+	 * simplest to the most complex as to see the more complex state
+	 * requires having the more basic state, due to how Intel designed the
+	 * xsave state.
+	 */
+	if (xinfo->xri_xsave == NULL) {
+		mdb_warn("missing required xsave information: xregs not "
+		    "usable\n");
+		return (B_FALSE);
+	}
+
+	xinfo->xri_type = XMM;
+	if (xinfo->xri_ymm != NULL) {
+		uint_t nzmm = 0;
+		if (xinfo->xri_opmask != NULL)
+			nzmm++;
+		if (xinfo->xri_zmm != NULL)
+			nzmm++;
+		if (xinfo->xri_hi_zmm != NULL)
+			nzmm++;
+		if (nzmm == 3) {
+			xinfo->xri_type = ZMM;
+		} else if (nzmm == 0) {
+			xinfo->xri_type = YMM;
+		} else {
+			mdb_warn("encountered mismatched AVX-512 components, "
+			    "defaulting back to YMM\n");
+			mdb_warn("found opmask %s, zmm %s, hi zmm %s\n",
+			    xinfo->xri_opmask != NULL ? "present" : "missing",
+			    xinfo->xri_zmm != NULL ? "present" : "missing",
+			    xinfo->xri_hi_zmm != NULL ? "present" : "missing");
+		}
+	}
+
+	return (B_TRUE);
+}
+
+static void
+pt_xreg_single_vector(const upad128_t *xmm, const upad128_t *ymm,
+    const upad256_t *zmm, uint32_t num)
+{
+
+	if (zmm != NULL) {
+		mdb_printf("%%zmm%u%s[511:384] 0x%08x %08x %08x %08x\n"
+		    "       [383:256] 0x%08x %08x %08x %08x\n", num,
+		    num >= 10 ? " " : "  ",
+		    zmm->_l[7], zmm->_l[6], zmm->_l[5], zmm->_l[4],
+		    zmm->_l[3], zmm->_l[2], zmm->_l[1], zmm->_l[0]);
+	}
+
+	if (ymm != NULL) {
+		mdb_printf("%%ymm%u%s[255:128] 0x%08x %08x %08x %08x\n",
+		    num, num >= 10 ? " " : "  ",
+		    ymm->_l[3], ymm->_l[2], ymm->_l[1], ymm->_l[0]);
+	}
+
+	if (xmm != NULL) {
+		mdb_printf("%%xmm%u%s[127:0]   0x%08x %08x %08x %08x\n",
+		    num, num >= 10 ? " " : "  ",
+		    xmm->_l[3], xmm->_l[2], xmm->_l[1], xmm->_l[0]);
+	}
+
+	/*
+	 * Insert output spacing if we exceed more than one line which happens
+	 * if ymm state is present.
+	 */
+	if (ymm != NULL) {
+		mdb_printf("\n");
+	}
+}
+
+/*
+ * Variant of the above, but all of the data is one single register. This is
+ * only used for the high zmm registers which are only present on amd64.
+ */
+#ifdef __amd64
+static void
+pt_xreg_single_u512(const upad512_t *zmm, uint32_t num)
+{
+	mdb_printf("%%zmm%u%s[511:384] 0x%08x %08x %08x %08x\n"
+	    "       [383:256] 0x%08x %08x %08x %08x\n", num,
+	    num >= 10 ? " " : "  ",
+	    zmm->_l[15], zmm->_l[14], zmm->_l[13], zmm->_l[12],
+	    zmm->_l[11], zmm->_l[10], zmm->_l[9], zmm->_l[8]);
+
+	mdb_printf("%%zmm%u%s[255:128] 0x%08x %08x %08x %08x\n",
+	    num, num >= 10 ? " " : "  ",
+	    zmm->_l[7], zmm->_l[6], zmm->_l[5], zmm->_l[4]);
+
+	mdb_printf("%%zmm%u%s[127:0]   0x%08x %08x %08x %08x\n",
+	    num, num >= 10 ? " " : "  ",
+	    zmm->_l[3], zmm->_l[2], zmm->_l[1], zmm->_l[0]);
+
+	mdb_printf("\n");
+}
+#endif	/* __amd64 */
+
+/*
+ * There are two different cases that we need to consider for vector printing.
+ * The first 16 FPU registers are shadowed as the low bits of xmm0 overlap with
+ * ymm0, overlap with zmm0.
+ */
+static void
+pt_xregs_vectors(const x86_xregs_info_t *xinfo)
+{
+	size_t nregs = ARRAY_SIZE(xinfo->xri_xsave->prx_fx_xmm);
+	for (size_t i = 0; i < nregs; i++) {
+		switch (xinfo->xri_type) {
+		case XMM:
+			pt_xreg_single_vector(&xinfo->xri_xsave->prx_fx_xmm[i],
+			    NULL, NULL, i);
+			break;
+		case YMM:
+			pt_xreg_single_vector(&xinfo->xri_xsave->prx_fx_xmm[i],
+			    &xinfo->xri_ymm->prx_ymm[i], NULL, i);
+			break;
+		case ZMM:
+			pt_xreg_single_vector(&xinfo->xri_xsave->prx_fx_xmm[i],
+			    &xinfo->xri_ymm->prx_ymm[i],
+			    &xinfo->xri_zmm->prx_zmm[i], i);
+			break;
+		}
+	}
+
+	/*
+	 * If we have ZMM state, next print the remaining 16 registers and then
+	 * the 8 opmask registers. Note, we only have the high ZMM registers on
+	 * 64-bit processes.
+	 */
+	if (xinfo->xri_type == ZMM) {
+#ifdef __amd64
+		nregs = ARRAY_SIZE(xinfo->xri_hi_zmm->prx_hi_zmm);
+		for (size_t i = 0; i < nregs; i++) {
+			pt_xreg_single_u512(&xinfo->xri_hi_zmm->prx_hi_zmm[i],
+			    i + 16);
+		}
+#endif	/* __amd64 */
+
+		mdb_printf("%%k0  0x%016x\t\t%%k1  0x%016x\n",
+		    xinfo->xri_opmask->prx_opmask[0],
+		    xinfo->xri_opmask->prx_opmask[1]);
+		mdb_printf("%%k2  0x%016x\t\t%%k3  0x%016x\n",
+		    xinfo->xri_opmask->prx_opmask[2],
+		    xinfo->xri_opmask->prx_opmask[3]);
+		mdb_printf("%%k4  0x%016x\t\t%%k5  0x%016x\n",
+		    xinfo->xri_opmask->prx_opmask[4],
+		    xinfo->xri_opmask->prx_opmask[5]);
+		mdb_printf("%%k6  0x%016x\t\t%%k7  0x%016x\n",
+		    xinfo->xri_opmask->prx_opmask[6],
+		    xinfo->xri_opmask->prx_opmask[7]);
+
+		mdb_printf("\n");
+	}
+}
+
+int
+x86_pt_fpregs_common(uintptr_t addr, uint_t flags, int argc,
+    prfpregset_t *fprsp)
+{
+	mdb_tgt_t *t = mdb.m_target;
+	mdb_tgt_tid_t tid;
+	prxregset_t *xregs = NULL;
+	size_t xregsize = 0;
+	x86_xregs_info_t xinfo;
+	x86_vector_type_t vector_type = XMM;
+
+	if (argc != 0)
+		return (DCMD_USAGE);
+
+	if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_UNDEAD) {
+		mdb_warn("no process active\n");
+		return (DCMD_ERR);
+	}
+
+	if (Pstate(t->t_pshandle) == PS_LOST) {
+		mdb_warn("debugger has lost control of process\n");
+		return (DCMD_ERR);
+	}
+
+	if (flags & DCMD_ADDRSPEC)
+		tid = (mdb_tgt_tid_t)addr;
+	else
+		tid = PTL_TID(t);
+
+	/*
+	 * We ultimately need both the xregs and the fpregs. The fpregs have
+	 * included synthetic-kernel created state that is not part of the FPU
+	 * (the status / xstatus bits). If we find the xregs state, then we
+	 * focus on using its data in lieu of the standard fxsave piece.
+	 */
+	if (PTL_GETFPREGS(t, tid, fprsp) != 0) {
+		mdb_warn("failed to get floating point registers");
+		return (DCMD_ERR);
+	}
+
+	bzero(&xinfo, sizeof (x86_xregs_info_t));
+	if (PTL_GETXREGS(t, tid, &xregs, &xregsize) == 0) {
+		prxregset_hdr_t *prx = (prxregset_hdr_t *)xregs;
+		if (!pt_xregs_process(prx, xregsize, &xinfo)) {
+			PTL_FREEXREGS(t, xregs, xregsize);
+			xregs = NULL;
+		} else {
+			vector_type = xinfo.xri_type;
+		}
+	} else if (errno != ENOENT && errno != ENODATA && errno != ENOTSUP) {
+		mdb_warn("failed to get xregs");
+	}
+
+	/*
+	 * We ultimately need both the xregs and the fpregs. The fpregs have
+	 * included synthetic-kernel created state that is not part of the FPU
+	 * (the status / xstatus bits). If we find the xregs state, then we
+	 * focus on using its data in lieu of the standard fxsave piece.
+	 */
+	if (PTL_GETFPREGS(t, tid, fprsp) != 0) {
+		mdb_warn("failed to get floating point registers");
+		return (DCMD_ERR);
+	}
+
+	bzero(&xinfo, sizeof (x86_xregs_info_t));
+	if (PTL_GETXREGS(t, tid, &xregs, &xregsize) == 0) {
+		prxregset_hdr_t *prx = (prxregset_hdr_t *)xregs;
+		if (!pt_xregs_process(prx, xregsize, &xinfo)) {
+			PTL_FREEXREGS(t, xregs, xregsize);
+			xregs = NULL;
+		} else {
+			vector_type = xinfo.xri_type;
+		}
+	} else if (errno != ENOENT && errno != ENODATA && errno != ENOTSUP) {
+		mdb_warn("failed to get xregs");
+	}
+
+	/*
+	 * As we only support the amd64 kernel, we basically phrase the FPU the
+	 * same way regardless of whether it is 32-bit or 64-bit now.
+	 */
+	mdb_printf("x86 FPU with %s registers\n", fp_type_to_str(vector_type));
+	if (xinfo.xri_xcr != NULL) {
+		mdb_printf("xcr0\t\t0x%lx\n", xinfo.xri_xcr->prx_xcr_xcr0);
+		mdb_printf("xfd\t\t0x%lx\n", xinfo.xri_xcr->prx_xcr_xfd);
+	}
+
+	if (xinfo.xri_xsave != NULL) {
+		mdb_printf("xstate_bv\t0x%lx\n",
+		    xinfo.xri_xsave->prx_xsh_xstate_bv);
+		mdb_printf("xcomp_bv\t0x%lx\n",
+		    xinfo.xri_xsave->prx_xsh_xcomp_bv);
+
+		mdb_printf("\n");
+		/*
+		 * xsave is required for us to use the xregset, so from here as
+		 * it to print vectors.
+		 */
+		pt_xregs_vectors(&xinfo);
+	} else {
+		size_t nregs = ARRAY_SIZE(fprsp->fp_reg_set.fpchip_state.xmm);
+		for (uint32_t i = 0; i < nregs; i++) {
+			const upad128_t *u128 =
+			    &fprsp->fp_reg_set.fpchip_state.xmm[i];
+			pt_xreg_single_vector(u128, NULL, NULL, i);
+		}
+
+		mdb_printf("\n");
+	}
+
+	if (xregs != NULL) {
+		PTL_FREEXREGS(t, xregs, xregsize);
+	}
+
+	return (DCMD_OK);
+}
+
+void
+x86_pt_fpregs_sse_ctl(uint32_t mxcsr, uint32_t xstatus, char *buf,
+    size_t buflen)
+{
+	mdb_printf("\nSSE Control State\n");
+	mdb_printf("mxcsr  0x%04x (%s)\n", mxcsr,
+	    fpmxcsr2str(mxcsr, buf, buflen));
+	mdb_printf("xcp    0x%04x (%s)\n", xstatus,
+	    fpmxcsr2str(xstatus, buf, buflen));
+}
--- /dev/null	Wed Apr 12 20:33:20 2023
+++ new/usr/src/cmd/mdb/intel/mdb/proc_x86util.h	Wed Apr 12 20:33:20 2023
@@ -0,0 +1,66 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+#ifndef _PROC_X86UTIL_H
+#define	_PROC_X86UTIL_H
+
+#include <mdb/mdb.h>
+
+#include <procfs.h>
+#include <sys/frame.h>
+#include <sys/ucontext.h>
+#include <sys/fp.h>
+#include <ieeefp.h>
+
+/*
+ * Common routines used for x86 proc targets.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+	XMM,
+	YMM,
+	ZMM
+} x86_vector_type_t;
+
+typedef struct x86_xregs_info {
+	x86_vector_type_t xri_type;
+	const prxregset_xcr_t *xri_xcr;
+	const prxregset_xsave_t *xri_xsave;
+	const prxregset_ymm_t *xri_ymm;
+	const prxregset_opmask_t *xri_opmask;
+	const prxregset_zmm_t *xri_zmm;
+	const prxregset_hi_zmm_t *xri_hi_zmm;
+} x86_xregs_info_t;
+
+extern int x86_pt_fpregs_common(uintptr_t, uint_t, int, fpregset_t *);
+extern void x86_pt_fpregs_sse_ctl(uint32_t, uint32_t, char *, size_t);
+
+/*
+ * Utility functions for printing x87 / SSE state.
+ */
+extern const char *fpcw2str(uint32_t, char *, size_t);
+extern const char *fpsw2str(uint32_t, char *, size_t);
+extern const char *fpmxcsr2str(uint32_t, char *, size_t);
+extern const char *fptag2str(uint32_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PROC_X86UTIL_H */
--- old/usr/src/cmd/ptools/pflags/pflags.c	Wed Apr 12 20:33:20 2023
+++ new/usr/src/cmd/ptools/pflags/pflags.c	Wed Apr 12 20:33:20 2023
@@ -53,9 +53,6 @@
 static	char	*prwhy(int);
 static	char	*prwhat(int, int);
 static	void	dumpregs(const prgregset_t, int);
-#if defined(__sparc) && defined(_ILP32)
-static	void	dumpregs_v8p(const prgregset_t, const prxregset_t *, int);
-#endif
 
 static	char	*command;
 static	struct	ps_prochandle *Pr;
@@ -349,22 +346,10 @@
 
 	if (rflag) {
 		if (Pstate(Pr) == PS_DEAD || (arg->pflags & PR_STOPPED)) {
-#if defined(__sparc) && defined(_ILP32)
-			/*
-			 * If we're SPARC/32-bit, see if we can get extra
-			 * register state for this lwp.  If it's a v8plus
-			 * program, print the 64-bit register values.
-			 */
-			prxregset_t prx;
-
-			if (Plwp_getxregs(Pr, psp->pr_lwpid, &prx) == 0 &&
-			    prx.pr_type == XR_TYPE_V8P)
-				dumpregs_v8p(psp->pr_reg, &prx, is64);
-			else
-#endif	/* __sparc && _ILP32 */
-				dumpregs(psp->pr_reg, is64);
-		} else
+			dumpregs(psp->pr_reg, is64);
+		} else {
 			(void) printf("\tNot stopped, can't show registers\n");
+		}
 	}
 
 	return (0);
@@ -507,20 +492,6 @@
 	return (str);
 }
 
-#if defined(__sparc)
-static const char * const regname[NPRGREG] = {
-	" %g0", " %g1", " %g2", " %g3", " %g4", " %g5", " %g6", " %g7",
-	" %o0", " %o1", " %o2", " %o3", " %o4", " %o5", " %sp", " %o7",
-	" %l0", " %l1", " %l2", " %l3", " %l4", " %l5", " %l6", " %l7",
-	" %i0", " %i1", " %i2", " %i3", " %i4", " %i5", " %fp", " %i7",
-#ifdef __sparcv9
-	"%ccr", " %pc", "%npc", "  %y", "%asi", "%fprs"
-#else
-	"%psr", " %pc", "%npc", "  %y", "%wim", "%tbr"
-#endif
-};
-#endif	/* __sparc */
-
 #if defined(__amd64)
 static const char * const regname[NPRGREG] = {
 	"%r15", "%r14", "%r13", "%r12", "%r11", "%r10", " %r9", " %r8",
@@ -611,45 +582,3 @@
 	if (i % cols != 0)
 		(void) putchar('\n');
 }
-
-#if defined(__sparc) && defined(_ILP32)
-static void
-dumpregs_v8p(const prgregset_t reg, const prxregset_t *xreg, int is64)
-{
-	static const uint32_t zero[8] = { 0 };
-	int gr, xr, cols = 2;
-	uint64_t xval;
-
-	if (memcmp(xreg->pr_un.pr_v8p.pr_xg, zero, sizeof (zero)) == 0 &&
-	    memcmp(xreg->pr_un.pr_v8p.pr_xo, zero, sizeof (zero)) == 0) {
-		dumpregs(reg, is64);
-		return;
-	}
-
-	for (gr = R_G0, xr = XR_G0; gr <= R_G7; gr++, xr++) {
-		xval = (uint64_t)xreg->pr_un.pr_v8p.pr_xg[xr] << 32 |
-		    (uint64_t)(uint32_t)reg[gr];
-		(void) printf("  %s = 0x%.16" PRIX64, regname[gr], xval);
-		if ((gr + 1) % cols == 0)
-			(void) putchar('\n');
-	}
-
-	for (gr = R_O0, xr = XR_O0; gr <= R_O7; gr++, xr++) {
-		xval = (uint64_t)xreg->pr_un.pr_v8p.pr_xo[xr] << 32 |
-		    (uint64_t)(uint32_t)reg[gr];
-		(void) printf("  %s = 0x%.16" PRIX64, regname[gr], xval);
-		if ((gr + 1) % cols == 0)
-			(void) putchar('\n');
-	}
-
-	for (gr = R_L0; gr < NPRGREG; gr++) {
-		(void) printf("  %s =         0x%.8lX",
-		    regname[gr], (long)reg[gr]);
-		if ((gr + 1) % cols == 0)
-			(void) putchar('\n');
-	}
-
-	if (gr % cols != 0)
-		(void) putchar('\n');
-}
-#endif	/* __sparc && _ILP32 */
--- old/usr/src/cmd/truss/systable.c	Wed Apr 12 20:33:20 2023
+++ new/usr/src/cmd/truss/systable.c	Wed Apr 12 20:33:20 2023
@@ -681,6 +681,7 @@
 {"setcontext",	2, DEC, NOV, HID, HEX},				/* 1 */
 {"getustack",	2, DEC, NOV, HID, HEX},				/* 2 */
 {"setustack",	2, DEC, NOV, HID, HEX},				/* 3 */
+{"getcontext_extd",	2, DEC, NOV, HID, HEX},				/* 4 */
 };
 #define	NCTXCODE	(sizeof (ctxtable) / sizeof (struct systable))
 
--- old/usr/src/head/proc_service.h	Wed Apr 12 20:33:20 2023
+++ new/usr/src/head/proc_service.h	Wed Apr 12 20:33:20 2023
@@ -56,7 +56,8 @@
 	PS_BADLID,	/* bad lwp identifier */
 	PS_BADADDR,	/* bad address */
 	PS_NOSYM,	/* p_lookup() could not find given symbol */
-	PS_NOFREGS	/* FPU register set not available for given lwp */
+	PS_NOFREGS,	/* FPU register set not available for given lwp */
+	PS_NOXREGS	/* extended register set not available for given lwp */
 } ps_err_e;
 
 struct ps_prochandle;
@@ -118,11 +119,9 @@
 extern ps_err_e ps_lsetfpregs(struct ps_prochandle *,
 			lwpid_t, const prfpregset_t *);
 
-#if defined(__sparc) || defined(__sparcv9)
 extern ps_err_e ps_lgetxregsize(struct ps_prochandle *, lwpid_t, int *);
 extern ps_err_e ps_lgetxregs(struct ps_prochandle *, lwpid_t, caddr_t);
 extern ps_err_e ps_lsetxregs(struct ps_prochandle *, lwpid_t, caddr_t);
-#endif
 
 #if defined(__i386) || defined(__amd64)
 extern ps_err_e ps_lgetLDT(struct ps_prochandle *, lwpid_t, struct ssd *);
--- old/usr/src/head/stdalign.h	Wed Apr 12 20:33:20 2023
+++ new/usr/src/head/stdalign.h	Wed Apr 12 20:33:20 2023
@@ -10,46 +10,12 @@
  */
 
 /*
- * Copyright 2016 Joyent, Inc.
- * Copyright 2022 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #ifndef _STDALIGN_H
 #define	_STDALIGN_H
 
-/*
- * ISO/IEC C11 stdalign.h. This header is meant to provide definitions for the
- * alignas and alignof 'keywords' into the underlying compiler-understood value.
- * In addition, there are two macros that are meant to define that this process
- * has happened. C++11 added alignas/alignof as keywords and including this
- * header is meant to cause us to still have the _is_defined macros, but not
- * define this overall.
- *
- * Unlike other cases we don't use any symbol guards here (other than C++) and
- * just allow the implementation to either have _Alignas and _Alignof or not
- * have it and lead to a compiler error for the user. The main justification of
- * this is that this header is only defined in C11 (and newer). It's not defined
- * in other standards and just as if you include a non-standard header, in this
- * case we don't try to stop that (same as if you included something like
- * libdevinfo.h).
- */
+#include <sys/stdalign.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifndef	__cplusplus
-
-#define	alignas	_Alignas
-#define	alignof	_Alignof
-
-#endif	/* !__cplusplus */
-
-#define	__alignas_is_defined	1
-#define	__alignof_is_defined	1
-
-#ifdef __cplusplus
-}
-#endif
-
 #endif /* _STDALIGN_H */
--- old/usr/src/head/ucontext.h	Wed Apr 12 20:33:20 2023
+++ new/usr/src/head/ucontext.h	Wed Apr 12 20:33:20 2023
@@ -24,6 +24,7 @@
 
 /*
  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ * Copyright 2023 Oxide Computer Company
  *
  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
@@ -47,6 +48,7 @@
 #if !defined(_XPG4_2) || defined(__EXTENSIONS__)
 #include <sys/regset.h>
 #include <sys/siginfo.h>
+#include <sys/int_types.h>
 #endif
 
 #ifdef	__cplusplus
@@ -63,12 +65,18 @@
 
 extern int getcontext(ucontext_t *) __RETURNS_TWICE;
 #pragma unknown_control_flow(getcontext)
-extern int setcontext(const ucontext_t *) __NORETURN;
+extern int setcontext(const ucontext_t *);
 extern int swapcontext(ucontext_t *_RESTRICT_KYWD,
 		const ucontext_t *_RESTRICT_KYWD);
 extern void makecontext(ucontext_t *, void(*)(), int, ...);
 
 #if !defined(_XPG4_2) || defined(__EXTENSIONS__)
+extern ucontext_t *ucontext_alloc(uint32_t);
+extern void ucontext_free(ucontext_t *);
+extern int getcontext_extd(ucontext_t *, uint32_t) __RETURNS_TWICE;
+#pragma unknown_control_flow(getcontext_extd)
+extern int swapcontext_extd(ucontext_t *_RESTRICT_KYWD, uint32_t,
+		const ucontext_t *_RESTRICT_KYWD);
 extern int walkcontext(const ucontext_t *, int (*)(uintptr_t, int, void *),
     void *);
 extern int printstack(int);
--- old/usr/src/lib/libc/amd64/gen/makectxt.c	Wed Apr 12 20:33:20 2023
+++ new/usr/src/lib/libc/amd64/gen/makectxt.c	Wed Apr 12 20:33:20 2023
@@ -25,8 +25,12 @@
  */
 
 /*	Copyright (c) 1988 AT&T	*/
-/*	  All Rights Reserved  	*/
+/*	  All Rights Reserved	*/
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #pragma weak _makecontext = makecontext
 
 #include "lint.h"
@@ -33,6 +37,9 @@
 #include <stdarg.h>
 #include <ucontext.h>
 #include <sys/stack.h>
+#include <sys/auxv.h>
+#include <errno.h>
+#include "libc.h"
 
 /*
  * The ucontext_t that the user passes in must have been primed with a
@@ -119,4 +126,63 @@
 
 	(void) getcontext(&uc);
 	(void) setcontext(uc.uc_link);
+}
+
+/*
+ * This is the ISA-specific allocation logic for allocating and setting up an
+ * extended ucontext_t. In particular, right now we need to allocate and add
+ * space for the UC_XSAVE member if we have the appropriate hardware support.
+ * The i386 / amd64 versions could be consolidated in a single x86 impl, but we
+ * don't have that right now.
+ */
+ucontext_t *
+ucontext_alloc(uint32_t flags)
+{
+	boolean_t do_xsave = B_FALSE;
+	size_t to_alloc = sizeof (ucontext_t);
+	ucontext_t *ucp;
+
+	if (flags != 0) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	/*
+	 * This value isn't really 100% accurate. The xsave size is basically
+	 * the worst case that we can have. The XMM / xsave structures aren't
+	 * included in here, but are going to be enough to cover this. We can
+	 * probably try to do a little better and should consider asking the
+	 * kernel for something more accurate. In particular, the problem with
+	 * this is that it doesn't account for the right size of future-looking
+	 * dynamic things, but then again neither does rtld. We'll deal with
+	 * this when we have support for the xfd MSR and actually use it. For
+	 * more information see uts/intel/os/fpu.c's big theory statement.
+	 */
+	switch (___getauxval(AT_SUN_FPTYPE)) {
+	case AT_386_FPINFO_XSAVE:
+	case AT_386_FPINFO_XSAVE_AMD:
+		do_xsave = B_TRUE;
+		to_alloc += ___getauxval(AT_SUN_FPSIZE);
+		break;
+	default:
+		break;
+	}
+
+	ucp = calloc(1, to_alloc);
+	if (ucp == NULL) {
+		return (NULL);
+	}
+
+	if (do_xsave) {
+		uintptr_t addr = (uintptr_t)ucp;
+		ucp->uc_xsave = addr + sizeof (ucontext_t);
+	}
+
+	return (ucp);
+}
+
+void
+ucontext_free(ucontext_t *ucp)
+{
+	free(ucp);
 }
--- old/usr/src/lib/libc/amd64/gen/siglongjmp.c	Wed Apr 12 20:33:20 2023
+++ new/usr/src/lib/libc/amd64/gen/siglongjmp.c	Wed Apr 12 20:33:20 2023
@@ -24,11 +24,17 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #include "lint.h"
 #include <sys/types.h>
 #include <sys/ucontext.h>
 #include <setjmp.h>
 #include <ucontext.h>
+#include <strings.h>
+#include <upanic.h>
 #include "sigjmp_struct.h"
 #include "libc.h"
 
@@ -37,6 +43,7 @@
 void
 siglongjmp(sigjmp_buf env, int val)
 {
+	const char *msg = "siglongjmp(): setcontext() returned";
 	ucontext_t *ucp = SIGJMP2UCONTEXT(env);
 
 	if (val)
@@ -44,5 +51,10 @@
 	else
 		ucp->uc_mcontext.gregs[REG_R0] = 1;
 
+	/*
+	 * While unlikely, it is possible that setcontext() may fail for some
+	 * reason. If that happens, we will kill the process.
+	 */
 	(void) setcontext(ucp);
+	upanic(msg, strlen(msg) + 1);
 }
--- old/usr/src/lib/libc/amd64/sys/__getcontext.S	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/amd64/sys/__getcontext.S	Wed Apr 12 20:33:21 2023
@@ -24,6 +24,10 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 	.file	"__getcontext.s"
 
 /*
@@ -41,3 +45,11 @@
 	SYSCERROR
 	RETC
 	SET_SIZE(__getcontext)
+
+	ENTRY(__getcontext_extd)
+	movq	%rdi, %rsi
+	movq	$4, %rdi
+	SYSTRAP_RVAL1(context)
+	SYSCERROR
+	RETC
+	SET_SIZE(__getcontext_extd)
--- old/usr/src/lib/libc/amd64/sys/getcontext.S	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/amd64/sys/getcontext.S	Wed Apr 12 20:33:21 2023
@@ -24,6 +24,10 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 	.file	"getcontext.s"
 
 #include <sys/asm_linkage.h>
@@ -42,9 +46,9 @@
  * swapcontext() shares the most code with getcontext().
  */
 
-#define	GETCONTEXT_IMPL(offset)						\
+#define	GETCONTEXT_IMPL(offset, func)						\
 	pushq	%rdi;		/* preserve the ucontext_t pointer */	\
-	call	__getcontext;						\
+	call	func;						\
 				/* call getcontext: syscall */		\
 	popq	%rdx;							\
 	andl	%eax, %eax;	/* if (error_return_from_syscall) */	\
@@ -69,26 +73,59 @@
 	movq	%rax, RAX_OFF (%rdx);					\
 				/* getcontext returns 0 after setcontext */
 
-/*	
- * getcontext(ucontext_t *ucp)
+/*
+ * int getcontext(ucontext_t *ucp)
  */
 
 	ENTRY(getcontext)
-	GETCONTEXT_IMPL(0)
+	GETCONTEXT_IMPL(0, __getcontext)
 	ret
 	SET_SIZE(getcontext)
 
 /*
- * swapcontext(ucontext_t *oucp, const ucontext_t *ucp)
+ * int swapcontext(ucontext_t *oucp, const ucontext_t *ucp)
  */
 
 	ENTRY(swapcontext)
 	pushq	%rsi			/* preserve the 2nd argument */
-	
-	GETCONTEXT_IMPL(8)
 
+	GETCONTEXT_IMPL(8, __getcontext)
+
 	/* call setcontext */
 	popq	%rdi
 	call	setcontext
 	ret
 	SET_SIZE(swapcontext)
+
+/*
+ * int getcontext_extd(ucontext_t * ctx, uint32_t flags)
+ */
+	ENTRY(getcontext_extd)
+	cmpl	$0, %esi
+	jne	2f
+	GETCONTEXT_IMPL(0, __getcontext_extd)
+	ret
+2:
+	movl	$EINVAL, %eax		/* errno = EINVAL */
+	jmp	__cerror		/* return (-1) */
+	SET_SIZE(getcontext_extd)
+
+/*
+ * int swapcontext_extd(ucontext_t *oucp, uint32_t flags, const ucontext_t *ucp)
+ */
+
+	ENTRY(swapcontext_extd)
+	cmpl	$0, %esi
+	jne	2f
+	pushq	%rdx			/* preserve the 3rd argument */
+
+	GETCONTEXT_IMPL(8, __getcontext_extd)
+
+	/* call setcontext */
+	popq	%rdi
+	call	setcontext
+	ret
+2:
+	movl	$EINVAL, %eax		/* errno = EINVAL */
+	jmp	__cerror		/* return (-1) */
+	SET_SIZE(swapcontext_extd)
--- old/usr/src/lib/libc/i386/fp/fpstart.c	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/i386/fp/fpstart.c	Wed Apr 12 20:33:21 2023
@@ -31,6 +31,10 @@
  */
 
 /*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
  * Establish the default settings for the floating-point state for a C language
  * program:
  *	rounding mode		-- round to nearest default by OS,
@@ -48,6 +52,7 @@
 #include	<sys/types.h>
 #include	<sys/sysi86.h>	/* for SI86FPHW/SI86FPSTART definitions */
 #include	<sys/fp.h>	/* for FPU_CW_INIT and SSE_MXCSR_INIT */
+#include	<upanic.h>
 
 extern int	__fltrounds();
 
@@ -55,6 +60,8 @@
 int	_sse_hw;		/* default: bss: 0 == no sse */
 int	__flt_rounds;		/* ANSI rounding mode */
 
+#define	UPANIC_MSG	"32-bit FPU init failed!"
+
 void
 __fpstart()
 {
@@ -64,14 +71,14 @@
 	 */
 	if ((_sse_hw = sysi86(SI86FPSTART,
 	    &_fp_hw, FPU_CW_INIT, SSE_MXCSR_INIT)) == -1) {
-		extern void _putcw();
-
 		/*
-		 * (fallback to old syscall on old kernels)
+		 * Because the system only supports a 64-bit kernel, the above
+		 * should always work (we assume it does in 64-bit libc). If it
+		 * should fail, we upanic. Note, because the FPU state is
+		 * unknown, we use a static message below and don't rely upon
+		 * strlen(), but rather constant time lengths.
 		 */
-		_sse_hw = 0;
-		(void) sysi86(SI86FPHW, &_fp_hw);
-		_putcw(0x133f);
+		upanic(UPANIC_MSG, sizeof (UPANIC_MSG));
 	}
 
 	/*
--- old/usr/src/lib/libc/i386/gen/makectxt.c	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/i386/gen/makectxt.c	Wed Apr 12 20:33:21 2023
@@ -25,8 +25,12 @@
  */
 
 /*	Copyright (c) 1988 AT&T	*/
-/*	  All Rights Reserved  	*/
+/*	  All Rights Reserved	*/
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #pragma weak _makecontext = makecontext
 
 #include "lint.h"
@@ -33,6 +37,9 @@
 #include <stdarg.h>
 #include <ucontext.h>
 #include <sys/stack.h>
+#include <sys/auxv.h>
+#include <errno.h>
+#include "libc.h"
 
 /*
  * The ucontext_t that the user passes in must have been primed with a
@@ -115,4 +122,63 @@
 
 	(void) getcontext(&uc);
 	(void) setcontext(uc.uc_link);
+}
+
+/*
+ * This is the ISA-specific allocation logic for allocating and setting up an
+ * extended ucontext_t. In particular, right now we need to allocate and add
+ * space for the UC_XSAVE member if we have the appropriate hardware support.
+ * The i386 / amd64 versions could be consolidated in a single x86 impl, but we
+ * don't have that right now.
+ */
+ucontext_t *
+ucontext_alloc(uint32_t flags)
+{
+	boolean_t do_xsave = B_FALSE;
+	size_t to_alloc = sizeof (ucontext_t);
+	ucontext_t *ucp;
+
+	if (flags != 0) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	/*
+	 * This value isn't really 100% accurate. The xsave size is basically
+	 * the worst case that we can have. The XMM / xsave structures aren't
+	 * included in here, but are going to be enough to cover this. We can
+	 * probably try to do a little better and should consider asking the
+	 * kernel for something more accurate. In particular, the problem with
+	 * tis is that it doesn't account for the right size of future-looking
+	 * dynamic things, but then again neither does rtld. We'll deal with
+	 * this when we have support for the xfd MSR and actually use it. For
+	 * more information see uts/intel/os/fpu.c's big theory statement.
+	 */
+	switch (___getauxval(AT_SUN_FPTYPE)) {
+	case AT_386_FPINFO_XSAVE:
+	case AT_386_FPINFO_XSAVE_AMD:
+		do_xsave = B_TRUE;
+		to_alloc += ___getauxval(AT_SUN_FPSIZE);
+		break;
+	default:
+		break;
+	}
+
+	ucp = calloc(1, to_alloc);
+	if (ucp == NULL) {
+		return (NULL);
+	}
+
+	if (do_xsave) {
+		uintptr_t addr = (uintptr_t)ucp;
+		ucp->uc_xsave = addr + sizeof (ucontext_t);
+	}
+
+	return (ucp);
+}
+
+void
+ucontext_free(ucontext_t *ucp)
+{
+	free(ucp);
 }
--- old/usr/src/lib/libc/i386/gen/siglongjmp.c	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/i386/gen/siglongjmp.c	Wed Apr 12 20:33:21 2023
@@ -28,11 +28,17 @@
 /*	Copyright (c) 1988 AT&T	*/
 /*	  All Rights Reserved	*/
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #include "lint.h"
 #include <sys/types.h>
 #include <sys/ucontext.h>
 #include <setjmp.h>
 #include <ucontext.h>
+#include <strings.h>
+#include <upanic.h>
 #include "libc.h"
 
 #pragma weak _siglongjmp = siglongjmp
@@ -40,6 +46,7 @@
 void
 siglongjmp(sigjmp_buf env, int val)
 {
+	const char *msg = "siglongjmp(): setcontext() returned";
 	ucontext_t *ucp = (ucontext_t *)env;
 
 	if (val)
@@ -47,5 +54,10 @@
 	else
 		ucp->uc_mcontext.gregs[EAX] = 1;
 
+	/*
+	 * While unlikely, it is possible that setcontext() may fail for some
+	 * reason. If that happens, we will kill the process.
+	 */
 	(void) setcontext(ucp);
+	upanic(msg, strlen(msg) + 1);
 }
--- old/usr/src/lib/libc/i386/sys/__getcontext.S	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/i386/sys/__getcontext.S	Wed Apr 12 20:33:21 2023
@@ -24,6 +24,10 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 	.file	"__getcontext.s"
 
 /*
@@ -44,3 +48,14 @@
 	SYSCERROR
 	RET
 	SET_SIZE(__getcontext)
+
+	ENTRY(__getcontext_extd)
+	popl	%edx
+	pushl	$4
+	pushl	%edx
+	SYSTRAP_RVAL1(context)
+	popl	%edx
+	movl	%edx, 0(%esp)
+	SYSCERROR
+	RET
+	SET_SIZE(__getcontext_extd)
--- old/usr/src/lib/libc/i386/sys/getcontext.S	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/i386/sys/getcontext.S	Wed Apr 12 20:33:21 2023
@@ -24,6 +24,10 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 	.file	"getcontext.s"
 
 #include <sys/asm_linkage.h>
@@ -44,10 +48,10 @@
  */
 
 
-#define	GETCONTEXT_IMPL							\
+#define	GETCONTEXT_IMPL(func)						\
 	movl	4(%esp), %eax;		/* %eax <-- first arg: ucp */	\
 	pushl	%eax;			/* push ucp for system call */	\
-	call	__getcontext;		/* call getcontext: syscall */	\
+	call	func;			/* call getcontext: syscall */	\
 	addl	$4, %esp;		/* pop arg */			\
 	andl	%eax, %eax;		/* if (err_ret_from_syscall) */	\
 	je	1f;							\
@@ -70,20 +74,20 @@
 				/* getcontext returns 0 after a setcontext */
 
 /*
- * getcontext(ucontext_t *ucp)
+ * int getcontext(ucontext_t *ucp)
  */
 	ENTRY(getcontext)
-	GETCONTEXT_IMPL
+	GETCONTEXT_IMPL(__getcontext)
 	ret
 	SET_SIZE(getcontext)
 
 
 /*
- * swapcontext(ucontext_t *oucp, const ucontext_t *ucp)
+ * int swapcontext(ucontext_t *oucp, const ucontext_t *ucp)
  */
 	ENTRY(swapcontext)
-	GETCONTEXT_IMPL
-	/ call setcontext
+	GETCONTEXT_IMPL(__getcontext)
+	/* call setcontext */
 	movl	8(%esp), %eax		/* %eax <-- second arg: ucp */
 	pushl	%eax			/* push ucp for setcontext */
 	call	setcontext
@@ -90,3 +94,37 @@
 	addl	$4, %esp		/* pop arg: just in case */
 	ret
 	SET_SIZE(swapcontext)
+
+/*
+ * int getcontext_extd(ucontext_t *ucp, uint32_t flags)
+ */
+	ENTRY(getcontext_extd)
+	movl	8(%esp), %eax
+	cmpl	$0, %eax		/* if (flags != 0) */
+	jne	2f
+	GETCONTEXT_IMPL(__getcontext_extd)
+	ret
+2:
+	movl	$EINVAL, %eax		/* errno = EINVAL; */
+	jmp	__cerror		/* return (-1) */
+	SET_SIZE(getcontext_extd)
+
+
+/*
+ * int swapcontext_extd(ucontext_t *oucp, const ucontext_t *ucp)
+ */
+	ENTRY(swapcontext_extd)
+	movl	8(%esp), %eax
+	cmpl	$0, %eax		/* if (flags != 0) */
+	jne	2f
+	GETCONTEXT_IMPL(__getcontext_extd)
+	/* call setcontext */
+	movl	12(%esp), %eax		/* %eax <-- second arg: ucp */
+	pushl	%eax			/* push ucp for setcontext */
+	call	setcontext
+	addl	$4, %esp		/* pop arg: just in case */
+	ret
+2:
+	movl	$EINVAL, %eax		/* errno = EINVAL; */
+	jmp	__cerror		/* return (-1) */
+	SET_SIZE(swapcontext_extd)
--- old/usr/src/lib/libc/inc/libc.h	Wed Apr 12 20:33:21 2023
+++ new/usr/src/lib/libc/inc/libc.h	Wed Apr 12 20:33:21 2023
@@ -230,6 +230,7 @@
  * defined in ucontext.s
  */
 extern int __getcontext(ucontext_t *);
+extern int __getcontext_extd(ucontext_t *);
 
 /*
  * defined in door.s
@@ -346,6 +347,12 @@
 extern size_t mbrtowc_nz(wchar_t *_RESTRICT_KYWD, const char *_RESTRICT_KYWD,
     size_t, mbstate_t *_RESTRICT_KYWD);
 
+/*
+ * defined in getauxv.c.
+ */
+extern long ___getauxval(int);
+extern void *___getauxptr(int);
+
 #ifdef	__cplusplus
 }
 #endif
--- old/usr/src/lib/libc/port/mapfile-vers	Wed Apr 12 20:33:22 2023
+++ new/usr/src/lib/libc/port/mapfile-vers	Wed Apr 12 20:33:22 2023
@@ -78,6 +78,14 @@
 $add amd64
 $endif
 
+SYMBOL_VERSION ILLUMOS_0.41 {
+    protected:
+	getcontext_extd;
+	swapcontext_extd;
+	ucontext_alloc;
+	ucontext_free;
+} ILLUMOS_0.40;
+
 SYMBOL_VERSION ILLUMOS_0.40 {
     protected:
 	errc;
--- old/usr/src/lib/libc_db/common/thread_db.c	Wed Apr 12 20:33:22 2023
+++ new/usr/src/lib/libc_db/common/thread_db.c	Wed Apr 12 20:33:22 2023
@@ -26,6 +26,7 @@
 
 /*
  * Copyright (c) 2014, Joyent, Inc. All rights reserved.
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <stdio.h>
@@ -1989,7 +1990,6 @@
 td_err_e
 __td_thr_getxregsize(td_thrhandle_t *th_p, int *xregsize)
 {
-#if defined(__sparc)
 	struct ps_prochandle *ph_p;
 	td_err_e return_val;
 
@@ -2003,12 +2003,12 @@
 	if (ps_lgetxregsize(ph_p, thr_to_lwpid(th_p), xregsize) != PS_OK)
 		return_val = TD_DBERR;
 
+	if (*xregsize == 0)
+		return_val = TD_NOXREGS;
+
 	(void) ps_pcontinue(ph_p);
 	ph_unlock(th_p->th_ta_p);
 	return (return_val);
-#else	/* __sparc */
-	return (TD_NOXREGS);
-#endif	/* __sparc */
 }
 
 /*
@@ -2015,13 +2015,12 @@
  * Get a thread's extra state register set.
  */
 #pragma weak td_thr_getxregs = __td_thr_getxregs
-/* ARGSUSED */
 td_err_e
 __td_thr_getxregs(td_thrhandle_t *th_p, void *xregset)
 {
-#if defined(__sparc)
 	struct ps_prochandle *ph_p;
 	td_err_e return_val;
+	ps_err_e ps_err;
 
 	if ((ph_p = ph_lock_th(th_p, &return_val)) == NULL)
 		return (return_val);
@@ -2030,15 +2029,15 @@
 		return (TD_DBERR);
 	}
 
-	if (ps_lgetxregs(ph_p, thr_to_lwpid(th_p), (caddr_t)xregset) != PS_OK)
+	ps_err = ps_lgetxregs(ph_p, thr_to_lwpid(th_p), (caddr_t)xregset);
+	if (ps_err == PS_NOXREGS)
+		return_val = TD_NOXREGS;
+	else if (ps_err != PS_OK)
 		return_val = TD_DBERR;
 
 	(void) ps_pcontinue(ph_p);
 	ph_unlock(th_p->th_ta_p);
 	return (return_val);
-#else	/* __sparc */
-	return (TD_NOXREGS);
-#endif	/* __sparc */
 }
 
 /*
@@ -2049,7 +2048,6 @@
 td_err_e
 __td_thr_setxregs(td_thrhandle_t *th_p, const void *xregset)
 {
-#if defined(__sparc)
 	struct ps_prochandle *ph_p;
 	td_err_e return_val;
 
@@ -2066,9 +2064,6 @@
 	(void) ps_pcontinue(ph_p);
 	ph_unlock(th_p->th_ta_p);
 	return (return_val);
-#else	/* __sparc */
-	return (TD_NOXREGS);
-#endif	/* __sparc */
 }
 
 struct searcher {
--- old/usr/src/lib/libproc/common/Pcontrol.c	Wed Apr 12 20:33:22 2023
+++ new/usr/src/lib/libproc/common/Pcontrol.c	Wed Apr 12 20:33:22 2023
@@ -28,7 +28,7 @@
  * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright 2015, Joyent, Inc.
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
- * Copyright 2021 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <assert.h>
@@ -3155,7 +3155,7 @@
  * or it will point to an empty slot for a new struct ps_lwphandle.
  */
 static struct ps_lwphandle **
-Lfind(struct ps_prochandle *P, lwpid_t lwpid)
+Lfind_slot(struct ps_prochandle *P, lwpid_t lwpid)
 {
 	struct ps_lwphandle **Lp;
 	struct ps_lwphandle *L;
@@ -3168,6 +3168,20 @@
 }
 
 /*
+ * A wrapper around Lfind_slot() that is suitable for the rest of the internal
+ * consumers who don't care about a slot, merely existence.
+ */
+struct ps_lwphandle *
+Lfind(struct ps_prochandle *P, lwpid_t lwpid)
+{
+	if (P->hashtab == NULL) {
+		return (NULL);
+	}
+
+	return (*Lfind_slot(P, lwpid));
+}
+
+/*
  * Grab an LWP contained within the controlled process.
  * Return an opaque pointer to its LWP control structure.
  *	perr: pointer to error return code.
@@ -3190,7 +3204,7 @@
 	    (P->hashtab = calloc(HASHSIZE, sizeof (struct ps_lwphandle *)))
 	    == NULL)
 		rc = G_STRANGE;
-	else if (*(Lp = Lfind(P, lwpid)) != NULL)
+	else if (*(Lp = Lfind_slot(P, lwpid)) != NULL)
 		rc = G_BUSY;
 	else if ((L = malloc(sizeof (struct ps_lwphandle))) == NULL)
 		rc = G_STRANGE;
@@ -3332,7 +3346,7 @@
 static void
 Lfree_internal(struct ps_prochandle *P, struct ps_lwphandle *L)
 {
-	*Lfind(P, L->lwp_id) = L->lwp_hash;	/* delete from hash table */
+	*Lfind_slot(P, L->lwp_id) = L->lwp_hash; /* delete from hash table */
 	if (L->lwp_ctlfd >= 0)
 		(void) close(L->lwp_ctlfd);
 	if (L->lwp_statfd >= 0)
@@ -3438,7 +3452,7 @@
  * Or, just get the current status (PCNULL).
  * Or, direct it to stop and get the current status (PCDSTOP).
  */
-static int
+int
 Lstopstatus(struct ps_lwphandle *L,
     long request,		/* PCNULL, PCDSTOP, PCSTOP, PCWSTOP */
     uint_t msec)		/* if non-zero, timeout in milliseconds */
--- old/usr/src/lib/libproc/common/Pcontrol.h	Wed Apr 12 20:33:22 2023
+++ new/usr/src/lib/libproc/common/Pcontrol.h	Wed Apr 12 20:33:22 2023
@@ -27,7 +27,7 @@
  * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright 2018 Joyent, Inc.
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
- * Copyright 2021 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #ifndef	_PCONTROL_H
@@ -144,9 +144,10 @@
 	lwpsinfo_t lwp_psinfo;	/* /proc/<pid>/lwp/<lwpid>/lwpsinfo data */
 	lwpstatus_t lwp_status;	/* /proc/<pid>/lwp/<lwpid>/lwpstatus data */
 	char lwp_name[THREAD_NAME_MAX];
+	prxregset_t *lwp_xregs;	/* /proc/<pid>/lwp/<lwpid>/xregs data */
+	size_t lwp_xregsize;
 #if defined(sparc) || defined(__sparc)
 	gwindows_t *lwp_gwins;	/* /proc/<pid>/lwp/<lwpid>/gwindows data */
-	prxregset_t *lwp_xregs;	/* /proc/<pid>/lwp/<lwpid>/xregs data */
 	int64_t *lwp_asrs;	/* /proc/<pid>/lwp/<lwpid>/asrs data */
 #endif
 } lwp_info_t;
@@ -298,6 +299,8 @@
 extern	int	Padd_mapping(struct ps_prochandle *, off64_t, file_info_t *,
     prmap_t *);
 extern	void	Psort_mappings(struct ps_prochandle *);
+extern	struct ps_lwphandle *Lfind(struct ps_prochandle *, lwpid_t);
+extern	int	Lstopstatus(struct ps_lwphandle *, long, uint_t);
 
 extern char	procfs_path[PATH_MAX];
 
--- old/usr/src/lib/libproc/common/Pcore.c	Wed Apr 12 20:33:23 2023
+++ new/usr/src/lib/libproc/common/Pcore.c	Wed Apr 12 20:33:23 2023
@@ -28,7 +28,7 @@
  * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright 2015 Gary Mills
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
- * Copyright 2021 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <sys/types.h>
@@ -1081,22 +1081,30 @@
 	return (0);
 }
 
-#ifdef __sparc
+/*
+ * The xregs are not a fixed size on all architectures (notably x86) and in
+ * general the prxregset_t has become opaque to deal with this. This means that
+ * validating the note itself can be a little more challenging. Especially as
+ * this can change across time. In this case we require that our consumers
+ * perform this validation right now ala what mdb does before using the xregs
+ * data.
+ */
 static int
 note_xreg(struct ps_prochandle *P, size_t nbytes)
 {
 	core_info_t *core = P->data;
 	lwp_info_t *lwp = core->core_lwp;
-	size_t xbytes = sizeof (prxregset_t);
 	prxregset_t *xregs;
+	ssize_t sret;
 
-	if (lwp == NULL || lwp->lwp_xregs != NULL || nbytes < xbytes)
+	if (lwp == NULL || lwp->lwp_xregs != NULL)
 		return (0);	/* No lwp yet, already seen, or bad size */
 
-	if ((xregs = malloc(xbytes)) == NULL)
+	if ((xregs = malloc(nbytes)) == NULL)
 		return (-1);
 
-	if (read(P->asfd, xregs, xbytes) != xbytes) {
+	sret = read(P->asfd, xregs, nbytes);
+	if (sret < 0 || (size_t)sret != nbytes) {
 		dprintf("Pgrab_core: failed to read NT_PRXREG\n");
 		free(xregs);
 		return (-1);
@@ -1103,9 +1111,11 @@
 	}
 
 	lwp->lwp_xregs = xregs;
+	lwp->lwp_xregsize = nbytes;
 	return (0);
 }
 
+#ifdef __sparc
 static int
 note_gwindows(struct ps_prochandle *P, size_t nbytes)
 {
@@ -1254,11 +1264,7 @@
 #else
 	note_notsup,		/*  3	NT_PRPSINFO (old)	*/
 #endif
-#ifdef __sparc
 	note_xreg,		/*  4	NT_PRXREG		*/
-#else
-	note_notsup,		/*  4	NT_PRXREG		*/
-#endif
 	note_platform,		/*  5	NT_PLATFORM		*/
 	note_auxv,		/*  6	NT_AUXV			*/
 #ifdef __sparc
--- old/usr/src/lib/libproc/common/Pgcore.c	Wed Apr 12 20:33:23 2023
+++ new/usr/src/lib/libproc/common/Pgcore.c	Wed Apr 12 20:33:23 2023
@@ -28,7 +28,7 @@
  * Copyright 2018 Joyent, Inc.
  * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
- * Copyright 2021 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #define	_STRUCTURED_PROC	1
@@ -443,16 +443,6 @@
 #endif	/* _LP64 */
 	}
 
-#ifdef sparc
-	{
-		prxregset_t xregs;
-		if (Plwp_getxregs(P, lsp->pr_lwpid, &xregs) == 0 &&
-		    write_note(pgc->pgc_fd, NT_PRXREG, &xregs,
-		    sizeof (prxregset_t), pgc->pgc_doff) != 0)
-			return (1);
-	}
-#endif	/* sparc */
-
 	return (0);
 }
 
@@ -495,40 +485,25 @@
 #endif	/* _LP64 */
 	}
 
-#ifdef sparc
 	{
-		prxregset_t xregs;
-		gwindows_t gwins;
+		prxregset_t *xregs;
 		size_t size;
 
-		if (Plwp_getxregs(P, lsp->pr_lwpid, &xregs) == 0) {
-			if (write_note(pgc->pgc_fd, NT_PRXREG, &xregs,
-			    sizeof (prxregset_t), pgc->pgc_doff) != 0)
-				return (1);
-		}
-
-		if (Plwp_getgwindows(P, lsp->pr_lwpid, &gwins) == 0 &&
-		    gwins.wbcnt > 0) {
-			size = sizeof (gwins) - sizeof (gwins.wbuf) +
-			    gwins.wbcnt * sizeof (gwins.wbuf[0]);
-
-			if (write_note(pgc->pgc_fd, NT_GWINDOWS, &gwins, size,
+		/*
+		 * While historically this function was only present on some
+		 * architectures (despite the presence of the empty file
+		 * elsewhere), if we call this on a platform without support
+		 * it'll now fail and thus is no longer subject to
+		 * platform-specific ifdefs.
+		 */
+		if (Plwp_getxregs(P, lsp->pr_lwpid, &xregs, &size) == 0) {
+			if (write_note(pgc->pgc_fd, NT_PRXREG, xregs, size,
 			    pgc->pgc_doff) != 0)
 				return (1);
-		}
 
-	}
-#ifdef __sparcv9
-	if (P->status.pr_dmodel == PR_MODEL_LP64) {
-		asrset_t asrs;
-		if (Plwp_getasrs(P, lsp->pr_lwpid, asrs) == 0) {
-			if (write_note(pgc->pgc_fd, NT_ASRS, &asrs,
-			    sizeof (asrset_t), pgc->pgc_doff) != 0)
-				return (1);
+			Plwp_freexregs(P, xregs, size);
 		}
 	}
-#endif	/* __sparcv9 */
-#endif	/* sparc */
 
 	if (Plwp_getname(P, lsp->pr_lwpid, name.pr_lwpname,
 	    sizeof (name.pr_lwpname)) == 0) {
--- old/usr/src/lib/libproc/common/Plwpregs.c	Wed Apr 12 20:33:23 2023
+++ new/usr/src/lib/libproc/common/Plwpregs.c	Wed Apr 12 20:33:23 2023
@@ -26,6 +26,7 @@
 /*
  * Copyright 2018 Joyent, Inc.
  * Copyright (c) 2013 by Delphix. All rights reserved.
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <sys/types.h>
@@ -39,9 +40,15 @@
 
 /*
  * This file implements the routines to read and write per-lwp register
- * information from either a live process or core file opened with libproc.
- * We build up a few common routines for reading and writing register
- * information, and then the public functions are all trivial calls to these.
+ * information from either a live process or core file opened with libproc.  We
+ * build up a few common routines for reading and writing register information,
+ * and then the public functions are all trivial calls to these.  It also
+ * implements similar logic that is used with an lwp handle.
+ *
+ * The primary registers and floating point registers (e.g. regs,fpregs) are
+ * retreived from the lwp and process status files.  The library caches the
+ * values of these files.  When we perorm updates, we ensure that cached copies
+ * are refreshed or updated as part of this.
  */
 
 /*
@@ -60,13 +67,14 @@
 			return (lwp);
 	}
 
-	errno = EINVAL;
+	errno = ENOENT;
 	return (NULL);
 }
 
 /*
  * Utility function to open and read the contents of a per-lwp /proc file.
- * This function is used to slurp in lwpstatus, xregs, and asrs.
+ * This function is used to slurp in lwpstatus, lwpname, lwpsinfo, spymaster,
+ * and others.
  */
 static int
 getlwpfile(struct ps_prochandle *P, lwpid_t lwpid,
@@ -83,12 +91,71 @@
 			(void) close(fd);
 			return (0);
 		}
+
+		int e = errno;
 		(void) close(fd);
+		errno = e;
 	}
 	return (-1);
 }
 
 /*
+ * This is a variant of getlwpfile that has three different semantics:
+ *
+ *  o It will stat the file to determine the size and allocate that for the
+ *    caller.
+ *  o If the stat size is zero (e.g. traditional xregs behavior when
+ *    unsupported) then it will return the libproc ENODATA error.
+ *  o It is an error if not all the data is read.
+ *
+ * Currently this is just used by xregs.
+ */
+static int
+getlwpfile_alloc(struct ps_prochandle *P, lwpid_t lwpid, const char *fbase,
+    void **datap, size_t *sizep)
+{
+	char fname[PATH_MAX];
+	int fd;
+
+	(void) snprintf(fname, sizeof (fname), "%s/%d/lwp/%d/%s",
+	    procfs_path, (int)P->status.pr_pid, (int)lwpid, fbase);
+
+	if ((fd = open(fname, O_RDONLY)) >= 0) {
+		int e;
+		struct stat st;
+
+		if (fstat(fd, &st) == 0) {
+			prxregset_t *prx;
+
+			if (st.st_size == 0) {
+				errno = ENODATA;
+				goto clean;
+			}
+
+			prx = malloc(st.st_size);
+			if (prx == NULL) {
+				goto clean;
+			}
+
+			if (read(fd, prx, st.st_size) == st.st_size) {
+				(void) close(fd);
+				*datap = prx;
+				*sizep = st.st_size;
+				return (0);
+			}
+
+			free(prx);
+		}
+clean:
+		e = errno;
+		(void) close(fd);
+		errno = e;
+	}
+
+	return (-1);
+}
+
+/*
  * Get the lwpstatus_t for an lwp from either the live process or our
  * cached information from the core file.  This is used to get the
  * general-purpose registers or floating point registers.
@@ -129,16 +196,49 @@
 }
 
 /*
+ * libproc caches information about the registers for representative LWPs and
+ * threads which we have the thread handle for. When we do a write to certain
+ * files, we need to refresh state and take care of both the process and the
+ * representative LWP's info. Because the xregs may or may not mutate the state
+ * of the other regsiters, we just always do a refresh of the entire cached
+ * psinfo.
+ */
+static void
+refresh_status(struct ps_prochandle *P, lwpid_t lwpid, struct ps_lwphandle *L,
+    long cmd, const void *rp, size_t n)
+{
+	if (P->status.pr_lwp.pr_lwpid == lwpid) {
+		if (cmd == PCSREG)
+			(void) memcpy(P->status.pr_lwp.pr_reg, rp, n);
+		else if (cmd == PCSFPREG)
+			(void) memcpy(&P->status.pr_lwp.pr_fpreg, rp, n);
+		else if (cmd == PCSXREG)
+			(void) Pstopstatus(P, PCNULL, 0);
+	}
+
+	if (L != NULL) {
+		if (cmd == PCSREG)
+			(void) memcpy(&L->lwp_status.pr_reg, rp, n);
+		else if (cmd == PCSFPREG)
+			(void) memcpy(&L->lwp_status.pr_fpreg, rp, n);
+		else if (cmd == PCSXREG)
+			(void) Lstopstatus(L, PCNULL, 0);
+	}
+}
+
+/*
  * Utility function to modify lwp registers.  This is done using either the
- * process control file or per-lwp control file as necessary.
+ * process control file or per-lwp control file as necessary.  This assumes that
+ * we have a process-level hold on things, which may not always be true.
  */
 static int
-setlwpregs(struct ps_prochandle *P, lwpid_t lwpid, long cmd,
+setlwpregs_proc(struct ps_prochandle *P, lwpid_t lwpid, long cmd,
     const void *rp, size_t n)
 {
 	iovec_t iov[2];
 	char fname[PATH_MAX];
-	int fd;
+	struct ps_lwphandle *L;
+	int fd = -1;
 
 	if (P->state != PS_STOP) {
 		errno = EBUSY;
@@ -151,22 +251,32 @@
 	iov[1].iov_len = n;
 
 	/*
+	 * If we have an lwp handle for this thread, then make sure that we use
+	 * that to update the state so cached information is updated.  We sync
+	 * the thread ahead of the process.
+	 */
+	if ((L = Lfind(P, lwpid)) != NULL) {
+		Lsync(L);
+		fd = L->lwp_ctlfd;
+	}
+
+	/*
 	 * Writing the process control file writes the representative lwp.
 	 * Psync before we write to make sure we are consistent with the
 	 * primary interfaces.  Similarly, make sure to update P->status
-	 * afterward if we are modifying one of its register sets.
+	 * afterward if we are modifying one of its register sets.  On some
+	 * platforms the xregs modify the other register states.  As a result,
+	 * always refresh the representative LWP's status.
 	 */
 	if (P->status.pr_lwp.pr_lwpid == lwpid) {
 		Psync(P);
+		fd = P->ctlfd;
+	}
 
-		if (writev(P->ctlfd, iov, 2) == -1)
+	if (fd > -1) {
+		if (writev(fd, iov, 2) == -1)
 			return (-1);
-
-		if (cmd == PCSREG)
-			(void) memcpy(P->status.pr_lwp.pr_reg, rp, n);
-		else if (cmd == PCSFPREG)
-			(void) memcpy(&P->status.pr_lwp.pr_fpreg, rp, n);
-
+		refresh_status(P, lwpid, L, cmd, rp, n);
 		return (0);
 	}
 
@@ -182,11 +292,40 @@
 			(void) close(fd);
 			return (0);
 		}
+		int e = errno;
 		(void) close(fd);
+		errno = e;
 	}
 	return (-1);
 }
 
+/*
+ * This is a variant of the above that only assumes we have a hold on the thread
+ * as opposed to a process.
+ */
+static int
+setlwpregs_lwp(struct ps_lwphandle *L, long cmd, const void *rp, size_t n)
+{
+	iovec_t iov[2];
+
+	if (L->lwp_state != PS_STOP) {
+		errno = EBUSY;
+		return (-1);
+	}
+
+	iov[0].iov_base = (caddr_t)&cmd;
+	iov[0].iov_len = sizeof (long);
+	iov[1].iov_base = (caddr_t)rp;
+	iov[1].iov_len = n;
+
+	Lsync(L);
+	if (writev(L->lwp_ctlfd, iov, 2) == -1)
+		return (-1);
+	refresh_status(L->lwp_proc, L->lwp_id, L, cmd, rp, n);
+
+	return (0);
+}
+
 int
 Plwp_getregs(struct ps_prochandle *P, lwpid_t lwpid, prgregset_t gregs)
 {
@@ -200,12 +339,25 @@
 }
 
 int
+Lgetregs(struct ps_lwphandle *L, prgregset_t *gregs)
+{
+	(void) memcpy(gregs, L->lwp_status.pr_reg, sizeof (prgregset_t));
+	return (0);
+}
+
+int
 Plwp_setregs(struct ps_prochandle *P, lwpid_t lwpid, const prgregset_t gregs)
 {
-	return (setlwpregs(P, lwpid, PCSREG, gregs, sizeof (prgregset_t)));
+	return (setlwpregs_proc(P, lwpid, PCSREG, gregs, sizeof (prgregset_t)));
 }
 
 int
+Lsetregs(struct ps_lwphandle *L, const prgregset_t *gregs)
+{
+	return (setlwpregs_lwp(L, PCSREG, gregs, sizeof (prgregset_t)));
+}
+
+int
 Plwp_getfpregs(struct ps_prochandle *P, lwpid_t lwpid, prfpregset_t *fpregs)
 {
 	lwpstatus_t lps;
@@ -217,16 +369,43 @@
 	return (0);
 }
 
-int Plwp_setfpregs(struct ps_prochandle *P, lwpid_t lwpid,
+int
+Lgetfpregs(struct ps_lwphandle *L, prfpregset_t *fpregs)
+{
+	(void) memcpy(fpregs, &L->lwp_status.pr_fpreg, sizeof (prfpregset_t));
+	return (0);
+}
+
+int
+Plwp_setfpregs(struct ps_prochandle *P, lwpid_t lwpid,
     const prfpregset_t *fpregs)
 {
-	return (setlwpregs(P, lwpid, PCSFPREG, fpregs, sizeof (prfpregset_t)));
+	return (setlwpregs_proc(P, lwpid, PCSFPREG, fpregs,
+	    sizeof (prfpregset_t)));
 }
 
-#if defined(sparc) || defined(__sparc)
 int
-Plwp_getxregs(struct ps_prochandle *P, lwpid_t lwpid, prxregset_t *xregs)
+Lsetfpregs(struct ps_lwphandle *L, const prfpregset_t *fpregs)
 {
+	return (setlwpregs_lwp(L, PCSFPREG, fpregs, sizeof (prfpregset_t)));
+}
+
+/*
+ * The reason that this is structured to take both the size and the process
+ * handle is so that way we have enough information to tie this back to its
+ * underlying source and we can eventually use umem with this.
+ */
+void
+Plwp_freexregs(struct ps_prochandle *P __unused, prxregset_t *prx,
+    size_t size __unused)
+{
+	free(prx);
+}
+
+int
+Plwp_getxregs(struct ps_prochandle *P, lwpid_t lwpid, prxregset_t **xregs,
+    size_t *sizep)
+{
 	lwp_info_t *lwp;
 
 	if (P->state == PS_IDLE) {
@@ -240,12 +419,17 @@
 			return (-1);
 		}
 
-		return (getlwpfile(P, lwpid, "xregs",
-		    xregs, sizeof (prxregset_t)));
+		return (getlwpfile_alloc(P, lwpid, "xregs",
+		    (void **)xregs, sizep));
 	}
 
-	if ((lwp = getlwpcore(P, lwpid)) != NULL && lwp->lwp_xregs != NULL) {
-		(void) memcpy(xregs, lwp->lwp_xregs, sizeof (prxregset_t));
+	if ((lwp = getlwpcore(P, lwpid)) != NULL && lwp->lwp_xregs != NULL &&
+	    lwp->lwp_xregsize > 0) {
+		*xregs = malloc(lwp->lwp_xregsize);
+		if (*xregs == NULL)
+			return (-1);
+		(void) memcpy(*xregs, lwp->lwp_xregs, lwp->lwp_xregsize);
+		*sizep = lwp->lwp_xregsize;
 		return (0);
 	}
 
@@ -255,12 +439,49 @@
 }
 
 int
-Plwp_setxregs(struct ps_prochandle *P, lwpid_t lwpid, const prxregset_t *xregs)
+Lgetxregs(struct ps_lwphandle *L, prxregset_t **xregs, size_t *sizep)
 {
-	return (setlwpregs(P, lwpid, PCSXREG, xregs, sizeof (prxregset_t)));
+	lwp_info_t *lwp;
+
+	if (L->lwp_state != PS_DEAD) {
+		if (L->lwp_state != PS_STOP) {
+			errno = EBUSY;
+			return (-1);
+		}
+		return (getlwpfile_alloc(L->lwp_proc, L->lwp_id, "xregs",
+		    (void **)xregs, sizep));
+	}
+
+	if ((lwp = getlwpcore(L->lwp_proc, L->lwp_id)) != NULL &&
+	    lwp->lwp_xregs != NULL && lwp->lwp_xregsize > 0) {
+		*xregs = malloc(lwp->lwp_xregsize);
+		if (*xregs == NULL)
+			return (-1);
+		(void) memcpy(*xregs, lwp->lwp_xregs, lwp->lwp_xregsize);
+		*sizep = lwp->lwp_xregsize;
+		return (0);
+	}
+
+	if (lwp != NULL)
+		errno = ENODATA;
+	return (-1);
 }
 
 int
+Plwp_setxregs(struct ps_prochandle *P, lwpid_t lwpid, const prxregset_t *xregs,
+    size_t len)
+{
+	return (setlwpregs_proc(P, lwpid, PCSXREG, xregs, len));
+}
+
+int
+Lsetxregs(struct ps_lwphandle *L, const prxregset_t *xregs, size_t len)
+{
+	return (setlwpregs_lwp(L, PCSXREG, xregs, len));
+}
+
+#if defined(sparc) || defined(__sparc)
+int
 Plwp_getgwindows(struct ps_prochandle *P, lwpid_t lwpid, gwindows_t *gwins)
 {
 	lwp_info_t *lwp;
@@ -324,7 +545,7 @@
 int
 Plwp_setasrs(struct ps_prochandle *P, lwpid_t lwpid, const asrset_t asrs)
 {
-	return (setlwpregs(P, lwpid, PCSASRS, asrs, sizeof (asrset_t)));
+	return (setlwpregs_proc(P, lwpid, PCSASRS, asrs, sizeof (asrset_t)));
 }
 #endif	/* __sparcv9 */
 #endif	/* __sparc */
--- old/usr/src/lib/libproc/common/Pservice.c	Wed Apr 12 20:33:23 2023
+++ new/usr/src/lib/libproc/common/Pservice.c	Wed Apr 12 20:33:23 2023
@@ -24,10 +24,12 @@
  */
 /*
  * Copyright (c) 2013 by Delphix. All rights reserved.
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <stdarg.h>
 #include <string.h>
+#include <errno.h>
 #include "Pcontrol.h"
 
 /*
@@ -167,8 +169,6 @@
 	return (PS_BADLID);
 }
 
-#if defined(sparc) || defined(__sparc)
-
 ps_err_e
 ps_lgetxregsize(struct ps_prochandle *P, lwpid_t lwpid, int *xrsize)
 {
@@ -182,10 +182,16 @@
 		for (lwp = list_head(&core->core_lwp_head); lwp != NULL;
 		    lwp = list_next(&core->core_lwp_head, lwp)) {
 			if (lwp->lwp_id == lwpid) {
-				if (lwp->lwp_xregs != NULL)
-					*xrsize = sizeof (prxregset_t);
-				else
+				if (lwp->lwp_xregs != NULL &&
+				    lwp->lwp_xregsize > 0) {
+					if (lwp->lwp_xregsize >= INT_MAX) {
+						return (PS_ERR);
+					}
+
+					*xrsize = (int)lwp->lwp_xregsize;
+				} else {
 					*xrsize = 0;
+				}
 				return (PS_OK);
 			}
 		}
@@ -199,6 +205,9 @@
 	if (stat(fname, &statb) != 0)
 		return (PS_BADLID);
 
+	if (statb.st_size > INT_MAX)
+		return (PS_ERR);
+
 	*xrsize = (int)statb.st_size;
 	return (PS_OK);
 }
@@ -206,18 +215,56 @@
 ps_err_e
 ps_lgetxregs(struct ps_prochandle *P, lwpid_t lwpid, caddr_t xregs)
 {
+	size_t xregsize;
+	prxregset_t *prx;
+
 	if (P->state != PS_STOP && P->state != PS_DEAD)
 		return (PS_ERR);
 
-	/* LINTED - alignment */
-	if (Plwp_getxregs(P, lwpid, (prxregset_t *)xregs) == 0)
+	if (Plwp_getxregs(P, lwpid, &prx, &xregsize) == 0) {
+		(void) memcpy(xregs, prx, xregsize);
+		Plwp_freexregs(P, prx, xregsize);
 		return (PS_OK);
+	}
 
+	if (errno == ENODATA)
+		return (PS_NOXREGS);
+
 	return (PS_BADLID);
 }
 
 ps_err_e
 ps_lsetxregs(struct ps_prochandle *P, lwpid_t lwpid, caddr_t xregs)
+{
+	size_t xregsize = 0;
+
+	if (P->state != PS_STOP)
+		return (PS_ERR);
+
+	/*
+	 * libproc asks the caller for the size of the extended register set.
+	 * Unfortunately, right now we aren't given the actual size of this
+	 * ourselves and we don't want to break the ABI that folks have used
+	 * historically. Therefore, we reach in and ask the structure in a
+	 * platform-specific way about what this should be. Sorry, this is a bit
+	 * unfortunate. This really shouldn't be a platform-specific #ifdef.
+	 */
+#if defined(__i386) || defined(__amd64)
+	prxregset_hdr_t *hdr = (prxregset_hdr_t *)xregs;
+	xregsize = hdr->pr_size;
+#endif
+	if (xregsize == 0)
+		return (PS_ERR);
+
+	if (Plwp_setxregs(P, lwpid, (prxregset_t *)xregs, xregsize) == 0)
+		return (PS_OK);
+
+	return (PS_BADLID);
+}
+
+#if defined(sparc) || defined(__sparc)
+ps_err_e
+ps_lsetxregs(struct ps_prochandle *P, lwpid_t lwpid, caddr_t xregs)
 {
 	if (P->state != PS_STOP)
 		return (PS_ERR);
--- old/usr/src/lib/libproc/common/libproc.h	Wed Apr 12 20:33:23 2023
+++ new/usr/src/lib/libproc/common/libproc.h	Wed Apr 12 20:33:23 2023
@@ -29,6 +29,7 @@
  * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright 2019, Carlos Neira <cneirabustos@gmail.com>
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*
@@ -333,6 +334,13 @@
 extern	int	Lmain_stack(struct ps_lwphandle *, stack_t *);
 extern	int	Lalt_stack(struct ps_lwphandle *, stack_t *);
 
+extern int	Lgetregs(struct ps_lwphandle *, prgregset_t *);
+extern int	Lsetregs(struct ps_lwphandle *, const prgregset_t *);
+extern int	Lgetfpregs(struct ps_lwphandle *, prfpregset_t *);
+extern int	Lsetfpregs(struct ps_lwphandle *, const prfpregset_t *);
+extern int	Lgetxregs(struct ps_lwphandle *, prxregset_t **, size_t *);
+extern int	Lsetxregs(struct ps_lwphandle *, const prxregset_t *, size_t);
+
 /*
  * Function prototypes for system calls forced on the victim process.
  */
@@ -419,11 +427,13 @@
 extern int Plwp_setfpregs(struct ps_prochandle *, lwpid_t,
     const prfpregset_t *);
 
-#if defined(__sparc)
+extern int Plwp_getxregs(struct ps_prochandle *, lwpid_t, prxregset_t **,
+    size_t *);
+extern void Plwp_freexregs(struct ps_prochandle *, prxregset_t *, size_t);
+extern int Plwp_setxregs(struct ps_prochandle *, lwpid_t, const prxregset_t *,
+    size_t);
 
-extern int Plwp_getxregs(struct ps_prochandle *, lwpid_t, prxregset_t *);
-extern int Plwp_setxregs(struct ps_prochandle *, lwpid_t, const prxregset_t *);
-
+#if defined(__sparc)
 extern int Plwp_getgwindows(struct ps_prochandle *, lwpid_t, gwindows_t *);
 
 #if defined(__sparcv9)
--- old/usr/src/lib/libproc/common/mapfile-vers	Wed Apr 12 20:33:24 2023
+++ new/usr/src/lib/libproc/common/mapfile-vers	Wed Apr 12 20:33:24 2023
@@ -25,6 +25,7 @@
 # Copyright (c) 2013 by Delphix. All rights reserved.
 # Copyright (c) 2019 Carlos Neira <cneirabustos@gmail.com>
 # Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2023 Oxide Computer Company
 #
 
 #
@@ -76,6 +77,9 @@
 	Ldstop;
 	Lfree;
 	Lgetareg;
+	Lgetfpregs;
+	Lgetregs;
+	Lgetxregs;
 	Lgrab;
 	Lgrab_error;
 	_libproc_debug	{ ASSERT = { TYPE = OBJECT; SIZE = 4; }; };
@@ -83,7 +87,10 @@
 	Lprochandle;
 	Lpsinfo;
 	Lputareg;
+	Lsetfpregs;
+	Lsetregs;
 	Lsetrun;
+	Lsetxregs;
 	Lstack;
 	Lstate;
 	Lstatus;
@@ -137,16 +144,19 @@
 	Plookup_by_addr;
 	Plookup_by_name;
 	Plwp_alt_stack;
+	Plwp_freexregs;
 	Plwp_getfpregs;
 	Plwp_getname;
 	Plwp_getpsinfo;
 	Plwp_getregs;
 	Plwp_getspymaster;
+	Plwp_getxregs;
 	Plwp_iter;
 	Plwp_iter_all;
 	Plwp_main_stack;
 	Plwp_setfpregs;
 	Plwp_setregs;
+	Plwp_setxregs;
 	Plwp_stack;
 	Pmapping_iter;
 	Pmapping_iter_resolved;
@@ -272,8 +282,11 @@
 	ps_lcontinue;
 	ps_lgetfpregs;
 	ps_lgetregs;
+	ps_lgetxregs;
+	ps_lgetxregsize;
 	ps_lsetfpregs;
 	ps_lsetregs;
+	ps_lsetxregs;
 	ps_lstop;
 	ps_pauxv;
 	ps_pbrandname;
@@ -332,12 +345,6 @@
 
 $if _sparc
 	Plwp_getgwindows;
-	Plwp_getxregs;
-	Plwp_setxregs;
-	ps_lgetxregs;
-	ps_lgetxregsize;
-	ps_lsetxregs;
-
 $if _ELF64
 	Plwp_getasrs;
 	Plwp_setasrs;
--- old/usr/src/man/man2/Makefile	Wed Apr 12 20:33:24 2023
+++ new/usr/src/man/man2/Makefile	Wed Apr 12 20:33:24 2023
@@ -202,6 +202,7 @@
 		futimesat.2			\
 		futimens.2			\
 		getaudit_addr.2			\
+		getcontext_extd.2		\
 		getegid.2			\
 		geteuid.2			\
 		getgid.2			\
@@ -320,6 +321,7 @@
 
 setauid.2			:= LINKSRC = getauid.2
 
+getcontext_extd.2		:= LINKSRC = getcontext.2
 setcontext.2			:= LINKSRC = getcontext.2
 
 setgroups.2			:= LINKSRC = getgroups.2
--- old/usr/src/man/man2/getcontext.2	Wed Apr 12 20:33:24 2023
+++ new/usr/src/man/man2/getcontext.2	Wed Apr 12 20:33:24 2023
@@ -44,12 +44,14 @@
 .\" Portions Copyright (c) 1992, X/Open Company Limited.  All Rights Reserved.
 .\" Copyright (c) 2001, Sun Microsystems, Inc.  All Rights Reserved.
 .\" Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
+.\" Copyright 2023 Oxide Computer Company
 .\"
-.Dd November 24, 2022
+.Dd January 24, 2022
 .Dt GETCONTEXT 2
 .Os
 .Sh NAME
 .Nm getcontext ,
+.Nm getcontext_extd ,
 .Nm setcontext
 .Nd get and set current user context
 .Sh SYNOPSIS
@@ -59,6 +61,11 @@
 .Fa "ucontext_t *ucp"
 .Fc
 .Ft int
+.Fo getcontext_extd
+.Fa "ucontext_t *ucp"
+.Fa "uint32_t flags"
+.Fc
+.Ft int
 .Fo setcontext
 .Fa "const ucontext_t *ucp"
 .Fc
@@ -76,6 +83,60 @@
 process' machine registers, the signal mask, and the current execution stack.
 .Pp
 The
+.Vt ucontext_t
+structure is a part of the system ABI.
+However, most architectures have added additional register states such as the
+extended vector and floating point registers that are not part of that.
+To facilitate getting that state
+.Pq such as the x86 xsave area
+the
+.Fn getcontext_extd
+function exists.
+Once called, the context will be initialized and is suitable for use in other
+context operations just as though one had called
+.Fn getcontext .
+.Pp
+Unlike the
+.Fn getcontext
+function,
+.Fn getcontext_extd
+assumes that callers have previously initialized
+.Fa ucp
+and thus it treats additional members
+.Po
+such as the
+.Fa uc_xsave
+member on x86
+.Pc
+as potentially valid.
+To allow for all extended states to be copied out,
+.Fa ucp
+must be allocated with
+.Xr ucontext_alloc 3C .
+Otherwise whether it is declared on the stack, as global data, allocated
+dynamically, or part of a structure,
+.Fa ucp
+must be zeroed through a call to
+.Xr bzero 3C
+or
+.Xr memset 3C
+prior to calling
+.Fn getcontext_extd .
+Improper initialization can lead to memory safety bugs, making it critical that
+this is done.
+.Pp
+The
+.Fa flags
+member must be zero and is present to allow for what is copied out to change in
+the future.
+This indicates that the system should attempt to copy out all extended states,
+though if the
+.Vt ucontext_t
+was not allocated with
+.Xr ucontext_alloc 3C ,
+some extended states may not be.
+.Pp
+The
 .Fn setcontext
 function restores the user context pointed to by
 .Fa ucp .
@@ -131,10 +192,27 @@
 .Fn setcontext
 does not return and
 .Fn getcontext
+and
+.Fn getcontext_extd
 returns 0.
 Otherwise, -1 is returned.
 .Sh ERRORS
-No errors are defined.
+No errors are defined for
+.Fn getcontext
+or
+.Fn setcontext .
+.Pp
+The
+.Fn getcontext_extd
+function only sets
+.Va errno
+in some circumstances when it fails.
+The function may fail if:
+.Bl -tag -width Er
+.It Er EINVAL
+.Fa flags
+had invalid values.
+.El
 .Sh USAGE
 When a signal handler is executed, the current user context is saved and a new
 context is created.
@@ -173,6 +251,7 @@
 .Xr makecontext 3C ,
 .Xr setjmp 3C ,
 .Xr sigsetjmp 3C ,
+.Xr ucontext_alloc 3C ,
 .Xr ucontext.h 3HEAD ,
 .Xr attributes 7 ,
 .Xr standards 7
--- old/usr/src/man/man3c/Makefile	Wed Apr 12 20:33:24 2023
+++ new/usr/src/man/man3c/Makefile	Wed Apr 12 20:33:24 2023
@@ -528,6 +528,7 @@
 		u8_validate.3c					\
 		ualarm.3c					\
 		uconv_u16tou32.3c				\
+		ucontext_alloc.3c				\
 		ucred.3c					\
 		ungetc.3c					\
 		ungetwc.3c					\
@@ -1305,6 +1306,7 @@
 		strtoumax.3c				\
 		strxfrm_l.3c				\
 		swapcontext.3c				\
+		swapcontext_extd.3c			\
 		swprintf.3c				\
 		swscanf.3c				\
 		tdelete.3c				\
@@ -1347,6 +1349,7 @@
 		uconv_u32tou8.3c			\
 		uconv_u8tou16.3c			\
 		uconv_u8tou32.3c			\
+		ucontext_free.3c			\
 		ucred_free.3c				\
 		ucred_get.3c				\
 		ucred_getegid.3c			\
@@ -2019,6 +2022,7 @@
 lfind.3c				:= LINKSRC = lsearch.3c
 
 swapcontext.3c				:= LINKSRC = makecontext.3c
+swapcontext_extd.3c			:= LINKSRC = makecontext.3c
 
 major.3c				:= LINKSRC = makedev.3c
 minor.3c				:= LINKSRC = makedev.3c
@@ -2499,6 +2503,8 @@
 
 ttyname_r.3c				:= LINKSRC = ttyname.3c
 
+ucontext_free.3c			:= LINKSRC = ucontext_alloc.3c
+
 uconv_u16tou8.3c			:= LINKSRC = uconv_u16tou32.3c
 uconv_u32tou16.3c			:= LINKSRC = uconv_u16tou32.3c
 uconv_u32tou8.3c			:= LINKSRC = uconv_u16tou32.3c
--- old/usr/src/man/man3c/makecontext.3c	Wed Apr 12 20:33:24 2023
+++ new/usr/src/man/man3c/makecontext.3c	Wed Apr 12 20:33:24 2023
@@ -43,86 +43,144 @@
 .\" Copyright 1989 AT&T
 .\" Portions Copyright (c) 1992, X/Open Company Limited.  All Rights Reserved.
 .\" Copyright (c) 2004, Sun Microsystems, Inc.  All Rights Reserved.
+.\" Copyright 2023 Oxide Computer Company
 .\"
-.TH MAKECONTEXT 3C "February 17, 2023"
-.SH NAME
-makecontext, swapcontext \- manipulate user contexts
-.SH SYNOPSIS
-.nf
-#include <ucontext.h>
-
-\fBvoid\fR \fBmakecontext\fR(\fBucontext_t *\fR\fIucp\fR, \fBvoid (*\fR\fIfunc\fR)(), \fBint\fR \fIargc\fR...);
-.fi
-
-.LP
-.nf
-\fBint\fR \fBswapcontext\fR(\fBucontext_t *restrict\fR \fIoucp\fR,
-     \fBconst ucontext_t *restrict\fR \fIucp\fR);
-.fi
-
-.SH DESCRIPTION
-The \fBmakecontext()\fR function modifies the context specified by \fIucp\fR,
-which has been initialized using \fBgetcontext\fR(2). When this context is
-resumed using \fBswapcontext()\fR or \fBsetcontext\fR(2), execution continues
-by calling the function \fIfunc\fR, passing it the arguments that follow
-\fIargc\fR in the \fBmakecontext()\fR call. The value of \fIargc\fR must match
-the number of pointer-sized integer arguments passed to \fIfunc\fR, otherwise
-the behavior is undefined.
-.sp
-.LP
-Before a call is made to \fBmakecontext()\fR, the context being modified should
-have a stack allocated for it. The stack is assigned to the context by
-initializing the \fBuc_stack\fR member.
-.sp
-.LP
-The \fBuc_link\fR member is used to determine the context that will be resumed
-when the context being modified by \fBmakecontext()\fR returns.  The
-\fBuc_link\fR member should be initialized prior to the call to
-\fBmakecontext()\fR. If the \fBuc_link\fR member is initialized to \fINULL\fR,
-the thread executing \fIfunc\fR will exit when \fIfunc\fR returns. See
-\fBpthread_exit\fR(3C).
-.sp
-.LP
-The \fBswapcontext()\fR function saves the current context in the context
-structure pointed to by \fIoucp\fR and sets the context to the context
-structure pointed to by \fIucp\fR.
-.sp
-.LP
-If the \fIucp\fR or \fIoucp\fR argument points to an invalid address, the
-behavior is undefined and \fBerrno\fR may be set to \fBEFAULT\fR.
-.SH RETURN VALUES
-On successful completion, \fBswapcontext()\fR returns \fB0\fR. Otherwise,
-\fB\(mi1\fR is returned and \fBerrno\fR is set to indicate the error.
-.SH ERRORS
-The \fBswapcontext()\fR function will fail if:
-.sp
-.ne 2
-.na
-\fB\fBENOMEM\fR\fR
-.ad
-.RS 10n
-The \fIucp\fR argument does not have enough stack left to complete the
-operation.
-.RE
-
-.sp
-.LP
-The \fBswapcontext()\fR function may fail if:
-.sp
-.ne 2
-.na
-\fB\fBEFAULT\fR\fR
-.ad
-.RS 10n
-The \fIucp\fR or \fIoucp\fR argument points to an invalid address.
-.RE
-
-.SH EXAMPLES
-\fBExample 1 \fRAlternate execution context on a stack whose memory was
-allocated using \fBmmap()\fR.
-.sp
-.in +2
-.nf
+.Dd March 20, 2023
+.Dt MAKECONTEXT 3C
+.Os
+.Sh NAME
+.Nm makecontext ,
+.Nm swapcontext ,
+.Nm swapcontext_extd
+.Nd manipulate user contexts
+.Sh SYNOPSIS
+.In ucontext.h
+.Ft void
+.Fo makecontext
+.Fa "ucontext_t *ucp"
+.Fa "void (*ifunc)()"
+.Fa "int argc"
+.Fa "..."
+.Fc
+.Ft int
+.Fo swapcontext
+.Fa "ucontext_t *restrict oucp"
+.Fa "const ucontext_t *restrict ucp"
+.Fc
+.Ft int
+.Fo swapcontext_extd
+.Fa "ucontext_t *restrict oucp"
+.Fa "uint32_t flags"
+.Fa "const ucontext_t *restrict ucp"
+.Fc
+.Sh DESCRIPTION
+The
+.Fn makecontext
+function modifies the context specified by
+.Fa ucp ,
+which has been initialized using
+.Xr getcontext 2 or
+.Xr getcontext_extd 2 .
+When this context is resumed using
+.Fn swapcontext ,
+.Fn swapcontext_extd ,
+or
+.Xr setcontext 2 ,
+execution continues by calling the function
+.Fa func ,
+passing it the arguments that follow
+.Fa argc
+in the
+.Fn makecontext
+call.
+The value of
+.Fa argc
+must match the number of pointer-sized integer arguments passed to
+.Fa func ,
+otherwise the behavior is undefined.
+.Pp
+Before a call is made to
+.Fn makecontext ,
+the context being modified should have a stack allocated for it.
+The stack is assigned to the context by initializing the
+.Fa uc_stack
+member.
+.Pp
+The
+.Fa uc_link
+member is used to determine the context that will be resumed when the context
+being modified by
+.Fn makecontext
+returns.
+The
+.Fa uc_link
+member should be initialized prior to the call to
+.Fn makecontext .
+If the
+.Fa uc_link
+member is initialized to
+.Dv NULL ,
+the thread executing
+.Fa func
+will exit when
+.Fa func returns.
+See
+.Xr pthread_exit 3C .
+.Pp
+The
+.Fn swapcontext
+function saves the current context in the context structure pointed to by
+.Fa oucp
+and sets the context to the context structure pointed to by
+.Fa ucp .
+.Pp
+If the
+.Fa ucp
+or
+.Fa oucp
+argument points to an invalid address, the behavior is undefined and
+.Va errno
+may be set to
+.Er EFAULT .
+.Pp
+The
+.Fn swapcontext_extd
+function is similar to
+.Fn swapcontext
+except that it performs a call to
+.Xr getcontext_extd 2
+to get and save the current context, passing the
+.Fa flags
+argument to
+.Xr getcontext_extd 2 .
+Note, the same constraints around the initialization of the
+.Vt ucontext_t
+that are discussed in
+.Xr getcontext_extd 2
+still apply.
+Mainly, the context must either have originally come from
+.Xr ucontext_alloc 3C
+or prior to its first use been zeroed.
+See
+.Xr getcontext_extd 2
+for more information.
+.Sh RETURN VALUES
+On successful completion,
+.Fn swapcontext
+and
+.Fn swapcontext_extd
+return
+.Sy 0 .
+Otherwise,
+.Sy -1
+is returned and
+.Va errno
+is set to indicate the error.
+.Sh EXAMPLES
+.Sy Example 1
+Alternate execution context on a stack whose memory was allocated using
+.Fn mmap .
+.Bd -literal -offset 2
 #include <stdio.h>
 #include <ucontext.h>
 #include <sys/mman.h>
@@ -157,61 +215,95 @@
 
         return (0);
 }
-.fi
-.in -2
-
-.SH USAGE
+.Ed
+.Sh ERRORS
+The
+.Fn swapcontext
+and
+.Fn swapcontext_extd
+function will fail if:
+.Bl -tag -width Er
+.It Er ENOMEM
+The
+.Fa ucp
+argument does not have enough stack left to complete the operation.
+.El
+.Pp
+The
+.Fn swapcontext
+and
+.Fn swapcontext_extd
+functions may fail if:
+.Bl -tag -width Er
+.It Er EFAULT
+The
+.Fa ucp
+or
+.Fa oucp
+argument points to an invalid address.
+.El
+.Pp
+The
+.Fn swapcontext_extd
+function may additionally fail if:
+.Bl -tag -width Er
+.It Er EINVAL
+The
+.Fa flags
+argument contains invalid values.
+.El
+.Sh USAGE
 These functions are useful for implementing user-level context switching
-between multiple threads of control within a process (co-processing). More
-effective multiple threads of control can be obtained by using native support
-for multithreading. See \fBthreads\fR(7).
-.SH ATTRIBUTES
-See \fBattributes\fR(7) for descriptions of the following attributes:
-.sp
-
-.sp
-.TS
-box;
-c | c
-l | l .
-ATTRIBUTE TYPE	ATTRIBUTE VALUE
-_
-Interface Stability	Standard
-_
-MT-Level	MT-Safe
-.TE
-
-.SH SEE ALSO
-.BR getcontext (2),
-.BR mmap (2),
-.BR sigaction (2),
-.BR sigprocmask (2),
-.BR pthread_exit (3C),
-.BR ucontext.h (3HEAD),
-.BR attributes (7),
-.BR standards (7),
-.BR threads (7)
-.SH NOTES
-The semantics of the \fBuc_stack\fR member of the \fBucontext_t\fR structure
-have changed as they apply to inputs to \fBmakecontext()\fR. Prior to Solaris
-10, the \fBss_sp\fR member of the \fBuc_stack\fR structure represented the high
-memory address of the area reserved for the stack. The \fBss_sp\fR member now
-represents the base (low memory address), in keeping with other uses of
-\fBss_sp\fR.
-.sp
-.LP
-This change in the meaning of \fBss_sp\fR is now the default behavior. The
-\fB-D__MAKECONTEXT_V2_SOURCE\fR compilation flag used in Solaris 9 update
-releases to access this behavior is obsolete.
-.sp
-.LP
+between multiple threads of control within a process
+.Pq co-processing .
+More effective multiple threads of control can be obtained by using native
+support for multithreading.
+See
+.Xr threads 7 .
+.Sh INTERFACE STABILITY
+.Sy Committed
+.Sh MT-LEVEL
+.Sy MT-Safe
+.Sh SEE ALSO
+.Xr getcontext 2 ,
+.Xr getcontext_extd 2 ,
+.Xr mmap 2 ,
+.Xr sigaction 2 ,
+.Xr sigprocmask 2 ,
+.Xr pthread_exit 3C ,
+.Xr ucontext_alloc 3C ,
+.Xr ucontext.h 3HEAD ,
+.Xr attributes 7 ,
+.Xr standards 7 ,
+.Xr threads 7
+.Sh NOTES
+The semantics of the
+.Fa uc_stack
+member of the
+.Fa ucontext_t
+structure have changed as they apply to inputs to
+.Fn makecontext .
+Prior to Solaris 10, the
+.Fa ss_sp
+member of the
+.Fa uc_stack
+tructure represented the high memory address of the area reserved for the stack.
+The
+.Fa ss_sp
+member now represents the base
+.Pq low memory address ,
+in keeping with other uses of
+.Fa ss_sp .
+This change in the meaning of
+.Fa ss_sp
+is the default behavior.
+.Pp
 Binary compatibility has been preserved with releases prior to Solaris 10.
-Before recompiling, applications that use \fBmakecontext()\fR must be updated
-to reflect this behavior change. The example below demonstrates a typical change
-that must be applied:
-.sp
-.in +2
-.nf
+Before recompiling, applications that use
+.Fn makecontext
+must be updated to reflect this behavior change.
+The example below demonstrates a typical change that must be applied:
+.Bd -literal -offset 2
 --- example1_s9.c       Thu Oct  3 11:58:17 2002
 +++ example1.c  Thu Jun 27 13:28:16 2002
 @@ -27,12 +27,9 @@
@@ -226,5 +318,4 @@
 
         makecontext(&uc, assign, 2, 100L, &value);
 .fi
-.in -2
-
+.Ed
--- /dev/null	Wed Apr 12 20:33:24 2023
+++ new/usr/src/man/man3c/ucontext_alloc.3c	Wed Apr 12 20:33:24 2023
@@ -0,0 +1,129 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2023 Oxide Computer Company
+.\"
+.Dd January 24, 2023
+.Dt UCONTEXT_ALLOC 3C
+.Os
+.Sh NAME
+.Nm ucontext_alloc ,
+.Nm ucontext_free
+.Nd allocate and free ucontext structures
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In ucontext.h
+.Ft "ucontext_t *"
+.Fo ucontext_alloc
+.Fa "uint32_t flags"
+.Fc
+.Ft void
+.Fo ucontext_free
+.Fa "ucontext_t *ucp"
+.Fc
+.Sh DESCRIPTION
+The
+.Fn ucontext_alloc
+function allocates and initializes a
+.Vt ucontext_t
+structure for subsequent use with
+.Xr getcontext_extd 2
+or
+.Xr swapcontext_extd 2 .
+.Pp
+Traditionally applications declare the
+.Vt ucontext_t
+structure on the stack, as part of another structure, or in other global data.
+Due to the advent of extended states
+.Pq such as the x86 xsave state
+the traditional structure is not sufficient to capture all state.
+The
+.Fn ucontext_alloc
+function determines the correct size for the current process to cover all of its
+extended states in addition to the standard
+.Vt ucontext_t
+and then proceeds to set up the other members of the
+.Vt ucontext_t
+to point at the additional memory.
+.Pp
+It is not recommended that the returned
+.Vt ucontext
+structure can be used with either
+.Xr getcontext 2
+or
+.Xr swapcontext 2 .
+While the resulting calls will work, they will not preserve that space for the
+extended state has been allocated.
+No memory will be leaked as a result of that.
+.Pp
+The
+.Fn ucontext_free
+function is used to release all the memory associated with
+.Fa ucp .
+.Fa ucp
+must have come from a prior call to
+.Fn ucontext_alloc .
+If it is not, then it is undefined as to what will happen to the program, but it
+will result in eventual memory corruption.
+If
+.Fa ucp
+was declared on the stack, as a structure member, as global data, or allocated
+in some way that wasn't calling
+.Fn ucontext_alloc ,
+do not pass it to
+.Fn ucontext_free .
+.Sh RETURN VALUES
+Upon successful completion, the
+.Fn ucontext_alloc
+function returns a pointer to an allocated
+.Vt ucontext_t .
+Otherwise
+.Dv NULL
+is returned and
+.Va errno
+is set to indicate the error.
+.Sh ERRORS
+The
+.Fn ucontext_alloc
+function will set
+.Va errno
+based on the failure of the underlying memory allocator.
+For more information and details on these errors, see
+.Xr malloc 3C ,
+the list of errors below may not be exhaustive.
+.Pp
+The
+.Fn ucontext_alloc
+function will fail if:
+.Bl -tag -width Er
+.It Er EINVAL
+The
+.Fa flags
+argument had unknown or unsupported values.
+.It Er ENOMEM
+There was insufficient memory to allocate an extended ucontext
+structure.
+See
+.Xr malloc 3C
+for more information.
+.It Er EAGAIN
+There was insufficient memory to allocate an extended ucontext
+structure, but the application could try again later.
+See
+.Xr malloc 3C
+for more information.
+.El
+.Sh INTERFACE STABILITY
+.Sy Committed
+.Sh MT-LEVEL
+.Sy Safe
+.Sh SEE ALSO
--- old/usr/src/man/man3c_db/td_thr_getgregs.3c_db	Wed Apr 12 20:33:25 2023
+++ new/usr/src/man/man3c_db/td_thr_getgregs.3c_db	Wed Apr 12 20:33:25 2023
@@ -1,184 +1,127 @@
-'\" te
+.\"
 .\"  Copyright (c) 1998 Sun Microsystems, Inc.  All Rights Reserved
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH TD_THR_GETGREGS 3C_DB "Oct 20, 1998"
-.SH NAME
-td_thr_getgregs, td_thr_setgregs, td_thr_getfpregs, td_thr_setfpregs,
-td_thr_getxregsize, td_thr_getxregs, td_thr_setxregs \- reading and writing
-thread registers in libc_db
-.SH SYNOPSIS
-.LP
-.nf
-cc [ \fIflag\fR... ] \fIfile\fR... -lc_db [ \fIlibrary\fR... ]
-#include <proc_service.h>
-#include <thread_db.h>
-
-\fBtd_err_e\fR \fBtd_thr_getgregs\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBprgregset_t\fR\fIgregset\fR);
-.fi
-
-.LP
-.nf
-\fBtd_err_e\fR \fBtd_thr_setgregs\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBprgregset_t\fR\fIgregset\fR);
-.fi
-
-.LP
-.nf
-\fBtd_err_e\fR \fBtd_thr_getfpregs\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBprfpregset_t *\fR\fIfpregset\fR);
-.fi
-
-.LP
-.nf
-\fBtd_err_e\fR \fBtd_thr_setfpregs\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBprfpregset_t *\fR\fIfpregset\fR);
-.fi
-
-.LP
-.nf
-\fBtd_err_e\fR \fBtd_thr_getxregsize\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBint *\fR\fIxregsize\fR);
-.fi
-
-.LP
-.nf
-\fBtd_err_e\fR \fBtd_thr_getxregs\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBprxregset_t *\fR\fIxregset\fR);
-.fi
-
-.LP
-.nf
-\fBtd_err_e\fR \fBtd_thr_setxregs\fR(\fBconst td_thrhandle_t *\fR\fIth_p\fR, \fBprxregset_t *\fR\fIxregset\fR);
-.fi
-
-.SH DESCRIPTION
-.sp
-.LP
+.\" Copyright 2023 Oxide Computer Company
+.Dd January 23, 2023
+.Dt TD_THR_GETGREGS 3C_DB
+.Os
+.Sh NAME
+.Nm td_thr_getgregs ,
+.Nm td_thr_setgregs ,
+.Nm td_thr_getfpregs ,
+.Nm td_thr_setfpregs ,
+.Nm td_thr_getxregsize ,
+.Nm td_thr_getxregs ,
+.Nm td_thr_setxregs
+.Nd reading and writing thread registers in libc_db
+.Sh LIBRARY
+.Lb libc_db
+.Sh SYNOPSIS
+.In proc_service.h
+.In thread_db.h
+.Ft td_err_e
+.Fo td_thr_getgregs
+.Fa "const td_thrhandle_t *th_p"
+.Fa "prgregset_t gregset"
+.Fc
+.Ft td_err_e
+.Fo td_thr_setgregs
+.Fa "const td_thrhandle_t *th_p"
+.Fa "prgregset_t gregset"
+.Fc
+.Ft td_err_e
+.Fo td_thr_getfpregs
+.Fa "const td_thrhandle_t *th_p"
+.Fa "prfpregset_t *fpregset"
+.Fc
+.Ft td_err_e
+.Fo td_thr_setfpregs
+.Fa "const td_thrhandle_t *th_p"
+.Fa "prfpregset_t *fpregset"
+.Fc
+.Ft td_err_e
+.Fo td_thr_getxregsize
+.Fa "const td_thrhandle_t *th_p"
+.Fa "int *xregsize"
+.Fc
+.Ft td_err_e
+.Fo td_thr_getxregs
+.Fa "const td_thrhandle_t *th_p"
+.Fa "prxregset_t *xregset"
+.Fc
+.Ft td_err_e
+.Fo td_thr_setxregs
+.Fa "const td_thrhandle_t *th_p"
+.Fa "prxregset_t *xregset"
+.Fc
+.Sh DESCRIPTION
 These functions read and write the register sets associated with thread
-\fIth_p\fR. The \fBtd_thr_getgregs()\fR and \fBtd_thr_setgregs()\fR functions
-get and set, respectively, the general registers of thread \fIth_p\fR. The
-\fBtd_thr_getfpregs()\fR and \fBtd_thr_setfpregs()\fR functions get and set,
-respectively, the thread's floating point register set. The
-\fBtd_thr_getxregsize()\fR, \fBtd_thr_getxregs()\fR, and
-\fBtd_thr_setxregs()\fR functions are SPARC-specific. The
-\fBtd_thr_getxregsize()\fR function returns in *\fIxregsize\fR the size of the
-architecture-dependent extra state registers. The \fBtd_thr_getxregs()\fR and
-\fBtd_thr_setxregs()\fR functions get and set, respectively, those extra state
-registers. On non-SPARC architectures, these functions return \fBTD_NOXREGS\fR.
-.sp
-.LP
-If the thread specified by \fIth_p\fR is currently executing on a lightweight
-process ( \fBLWP),\fR these functions read or write, respectively, the
-appropriate register set to the \fBLWP\fR using the imported interface. If the
-thread is not  currently executing on an \fBLWP\fR, the floating point and
-extra state registers may cannot be read or written. Some of the general
-registers might also not be readable or writable, depending on the
-architecture, in which case \fBtd_thr_getfpregs()\fR and
-\fBtd_thr_setfpregs()\fR return  \fBTD_NOFPREGS\fR and \fBtd_thr_getxregs()\fR
-and \fBtd_thr_setxregs()\fR will \fBTD_NOXREGS\fR. Calls to
-\fBtd_thr_getgregs()\fR and \fBtd_thr_setgregs()\fR succeed, but values
-returned for unreadable registers are undefined, values specified for
-unwritable registers are ignored. In this instance, and \fBTD_PARTIALREGS\fR is
-returned.  See the architecture-specific notes that follow regarding the
-registers that may be read and written for a thread not currently executing on
-an \fBLWP\fR.
-.SS "SPARC"
-.sp
-.LP
-On a thread not currently assigned to an \fBLWP\fR, only %i0-%i7, %l0-%l7, %g7,
-%pc, and %sp (%o6) can be read or written.  %pc and %sp refer to the program
-counter and stack pointer that the thread will have when it resumes execution.
-.SS "x86 Architecture"
-.sp
-.LP
-On a thread not currently assigned to an \fBLWP,\fR only %pc, %sp, %ebp, %edi,
-%edi, and %ebx can be read.
-.SH RETURN VALUES
-.sp
-.ne 2
-.na
-\fB\fBTD_OK\fR\fR
-.ad
-.RS 18n
+.Fa th_p .
+The
+.Fn td_thr_getgregs
+and
+.Fn td_thr_setgregs
+functions get and set, respectively, the general registers of thread
+.Fa th_p .
+The
+.Fn td_thr_getfpregs
+and
+.Fn td_thr_setfpregs
+functions get and set, respectively, the thread's floating point register set.
+.Pp
+The
+.Fn td_thr_getxregsize
+function returns in
+.Fa *xregsize
+the size of the
+architecture-dependent extra state registers.
+The
+.Fn td_thr_getxregs
+and
+.Fn td_thr_setxregs
+functions get and set, respectively, those extra state registers.
+The buffer passed to
+.Fn td_thr_getxregs
+must be at least the size indicated by
+.Fn td_thr_getxregsize .
+This size must not be assumed and may change between processes.
+Failure to do so, will result in the system overwriting memory.
+Not all platforms implement extended registers.
+When they are not implemented, these functions return
+.Dv TD_NOXREGS .
+.Sh RETURN VALUES
+The
+.Fn td_thr_getgregs ,
+.Fn td_thr_setgregs ,
+.Fn td_thr_getfpregs ,
+.Fn td_thr_setfpregs ,
+.Fn td_thr_getxregsize ,
+.Fn td_thr_getxregs ,
+and
+.Fn td_thr_setxregs
+functions return one of the following values:
+.Bl -tag -width Ds
+.It Dv TD_OK
 The call completed successfully.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTD_BADTH\fR\fR
-.ad
-.RS 18n
+.It Dv TD_BADTH
 An invalid thread handle was passed in.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTD_DBERR\fR\fR
-.ad
-.RS 18n
+.It Dv TD_DBERR
 A call to one of the imported interface routines failed.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTD_PARTIALREGS\fR\fR
-.ad
-.RS 18n
-Because the thread is not currently assigned to a \fBLWP,\fR not all registers
-were read or written. See \fBDESCRIPTION\fR for a discussion about which
-registers are not saved when a thread is not assigned to an \fBLWP\fR.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTD_NOFPREGS\fR\fR
-.ad
-.RS 18n
-Floating point registers could not be read or written, either because the
-thread is not currently assigned to an \fBLWP\fR, or because the architecture
+.It Dv TD_NOFPREGS
+Floating point registers could not be read or written because the architecture
 does not have such registers.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTD_NOXREGS\fR\fR
-.ad
-.RS 18n
+.It Dv TD_NOXREGS
 Architecture-dependent extra state registers could not be read or written,
-either because the thread is not currently assigned to an \fBLWP\fR, or because
-the architecture does not have such registers, or because the architecture is
-not a SPARC architecture.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTD_ERR\fR\fR
-.ad
-.RS 18n
-A \fBlibc_db\fR internal error occurred.
-.RE
-
-.SH ATTRIBUTES
-.sp
-.LP
-See \fBattributes\fR(7) for description of the following attributes:
-.sp
-
-.sp
-.TS
-box;
-c | c
-l | l .
-ATTRIBUTE TYPE	ATTRIBUTE VALUE
-_
-MT-Level	Safe
-.TE
-
-.SH SEE ALSO
-.sp
-.LP
-.BR libc_db (3LIB),
-.BR attributes (7)
+because the architecture does not have such registers.
+.It Dv TD_ERR
+A
+.Sy libc_db
+internal error occurred.
+.El
+.Sh MT-LEVEL
+.Sy Safe
+.Sh SEE ALSO
+.Xr libc_db 3LIB ,
+.Xr attributes 7
--- old/usr/src/man/man3lib/libproc.3lib	Wed Apr 12 20:33:25 2023
+++ new/usr/src/man/man3lib/libproc.3lib	Wed Apr 12 20:33:25 2023
@@ -12,8 +12,9 @@
 .\" Copyright 2018 Joyent, Inc.
 .\" Copyright (c) 2019 Carlos Neira <cneirabustos@gmail.com>
 .\" Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+.\" Copyright 2023 Oxide Computer Company
 .\"
-.Dd March 10, 2023
+.Dd March 20, 2023
 .Dt LIBPROC 3LIB
 .Os
 .Sh NAME
@@ -218,11 +219,6 @@
 .It Sy Pissyscall_prev Ta Sy Plmid
 .It Sy Plmid_to_loadobj Ta Sy Plmid_to_map
 .It Sy Plookup_by_addr Ta Sy Plookup_by_name
-.It Sy Plwp_alt_stack Ta Sy Plwp_getfpregs
-.It Sy Plwp_getname Ta Sy Plwp_getpsinfo
-.It Sy Plwp_getregs Ta Sy Plwp_getspymaster
-.It Sy Plwp_main_stack Ta Sy Plwp_setfpregs
-.It Sy Plwp_setregs Ta Sy Plwp_stack
 .It Sy Pname_to_ctf Ta Sy Pname_to_loadobj
 .It Sy Pname_to_map Ta Sy Pobjname
 .It Sy Pobjname_resolved Ta Sy Pplatform
@@ -256,14 +252,26 @@
 .It Sy Lalt_stack Ta Sy Lclearfault
 .It Sy Lclearsig Ta Sy Lctlfd
 .It Sy Ldstop Ta Sy Lgetareg
-.It Sy Lmain_stack Ta Sy Lprochandle
-.It Sy Lpsinfo Ta Sy Lputareg
-.It Sy Lsetrun Ta Sy Lstack
+.It Sy Lgetfpregs Ta Sy Lgetregs
+.It Sy Lgetxregs Ta Sy Lmain_stack
+.It Sy Lprochandle Ta Sy Lpsinfo
+.It Sy Lputareg Ta Sy Lsetrun
+.It Sy Lsetfpregs Ta Sy Lsetregs
+.It Sy Lsetxregs Ta Sy Lstack
 .It Sy Lstate Ta Sy Lstatus
 .It Sy Lstop Ta Sy Lsync
 .It Sy Lwait Ta Sy Lxecbkpt
-.It Sy Lxecwapt Ta ""
+.It Sy Lxecwapt Ta Sy ""
 .El
+.Bl -column -offset indent ".Sy Pmapping_iter_resolved" ".Sy Psymbol_iter_by_addr"
+.It Sy Plwp_alt_stack Ta Sy Plwp_freexregs
+.It Sy Plwp_getfpregs Ta Sy Plwp_getname
+.It Sy Plwp_getpsinfo Ta Sy Plwp_getregs
+.It Sy Plwp_getspymaster Ta Sy Plwp_main_stack
+.It Sy Plwp_getxregs Ta Sy Plwp_setfpregs
+.It Sy Plwp_setregs Ta Sy Plwp_setxregs
+.It Sy Plwp_stack Ta Sy ""
+.El
 .Ss System Call Injection
 The following routines are used to inject specific system calls and have
 them run in the context of a process.
@@ -338,8 +346,7 @@
 .Nm
 library.
 .Bl -column -offset indent ".Sy Pmapping_iter_resolved" ".Sy Psymbol_iter_by_addr"
-.It Sy Plwp_getgwindows Ta Sy Plwp_getxregs
-.It Sy Plwp_setxregs Ta Sy ""
+.It Sy Plwp_getgwindows Ta Sy ""
 .El
 .Pp
 The following functions are specific to the 64-bit SPARC version of the
--- old/usr/src/man/man3proc/Makefile	Wed Apr 12 20:33:25 2023
+++ new/usr/src/man/man3proc/Makefile	Wed Apr 12 20:33:25 2023
@@ -180,9 +180,15 @@
 		Lclearsig.3proc			\
 		Ldstop.3proc			\
 		Lgetareg.3proc			\
+		Lgetfpregs.3proc		\
+		Lgetregs.3proc			\
+		Lgetxregs.3proc			\
 		Lmain_stack.3proc		\
 		Lputareg.3proc			\
+		Lsetfpregs.3proc		\
+		Lsetregs.3proc			\
 		Lsetrun.3proc			\
+		Lsetxregs.3proc			\
 		Lstack.3proc			\
 		Lstop.3proc			\
 		Lsync.3proc			\
@@ -201,6 +207,7 @@
 		Plmid_to_map.3proc		\
 		Plookup_by_name.3proc		\
 		Plwp_alt_stack.3proc		\
+		Plwp_freexregs.3proc		\
 		Plwp_getfpregs.3proc		\
 		Plwp_iter_all.3proc		\
 		Plwp_main_stack.3proc		\
@@ -355,6 +362,10 @@
 Plwp_setregs.3proc		:= LINKSRC = Plwp_getregs.3proc
 Plwp_getfpregs.3proc		:= LINKSRC = Plwp_getregs.3proc
 Plwp_setfpregs.3proc		:= LINKSRC = Plwp_getregs.3proc
+Lgetfpregs.3proc		:= LINKSRC = Plwp_getregs.3proc
+Lsetfpregs.3proc		:= LINKSRC = Plwp_getregs.3proc
+Lgetregs.3proc			:= LINKSRC = Plwp_getregs.3proc
+Lsetregs.3proc			:= LINKSRC = Plwp_getregs.3proc
 
 Plwp_alt_stack.3proc		:= LINKSRC = Plwp_stack.3proc
 Plwp_main_stack.3proc		:= LINKSRC = Plwp_stack.3proc
@@ -431,7 +442,10 @@
 
 proc_get_ldt.3proc		:= LINKSRC = Pldt.3proc
 
+Plwp_freexregs.3proc		:= LINKSRC = Plwp_getxregs.3proc
 Plwp_setxregs.3proc		:= LINKSRC = Plwp_getxregs.3proc
+Lgetxregs.3proc			:= LINKSRC = Plwp_getxregs.3proc
+Lsetxregs.3proc			:= LINKSRC = Plwp_getxregs.3proc
 
 Plwp_setasrs.3proc		:= LINKSRC = Plwp_getasrs.3proc
 
--- old/usr/src/man/man3proc/Plwp_getregs.3proc	Wed Apr 12 20:33:25 2023
+++ new/usr/src/man/man3proc/Plwp_getregs.3proc	Wed Apr 12 20:33:25 2023
@@ -10,8 +10,9 @@
 .\"
 .\"
 .\" Copyright 2015 Joyent, Inc.
+.\" Copyright 2023 Oxide Computer Company
 .\"
-.Dd May 02, 2020
+.Dd January 19, 2023
 .Dt PLWP_GETREGS 3PROC
 .Os
 .Sh NAME
@@ -18,7 +19,11 @@
 .Nm Plwp_getfpregs ,
 .Nm Plwp_setfpregs ,
 .Nm Plwp_getregs ,
-.Nm Plwp_setregs
+.Nm Plwp_setregs ,
+.Nm Lgetfpregs ,
+.Nm Lsetfpregs ,
+.Nm Lgetregs ,
+.Nm Lsetregs
 .Nd get and set thread registers
 .Sh LIBRARY
 .Lb libproc
@@ -48,6 +53,26 @@
 .Fa "lwpid_t lwpid"
 .Fa "const prgregset_t gregs"
 .Fc
+.Ft int
+.Fo Lgetfpregs
+.Fa "struct ps_lwphandle *L"
+.Fa "prfpregset_t *fpregs"
+.Fc
+.Ft int
+.Fo Lsetfpregs
+.Fa "struct ps_lwphandle *L"
+.Fa "const prfpregset_t *fpregs"
+.Fc
+.Ft int
+.Fo Lgetregs
+.Fa "struct ps_lwphandle *L"
+.Fa "prgregset_t gregs"
+.Fc
+.Ft int
+.Fo Lsetregs
+.Fa "struct ps_lwphandle *L"
+.Fa "const prgregset_t gregs"
+.Fc
 .Sh DESCRIPTION
 The
 .Fn Plwp_getregs ,
@@ -105,13 +130,47 @@
 .Pp
 One may not set the register values of a process that is not an active
 process, e.g. a process handle that refers to a file or a core file.
+.Pp
+The
+.Fn Lgetfpregs ,
+.Fn Lsetfpregs ,
+.Fn Lgetregs ,
+and
+.Fn Lsetregs
+functions behave in a same way as the corresponding process-handle
+specific functions, with the following differences:
+.Bl -dash
+.It
+Rather than take a specified thread id via
+.Fa lwpid ,
+they operate on the thread that the thread-handle,
+.Fa L ,
+refers to.
+.It
+The process-handle functions will require that the entire process is
+stopped before performing a set operation.
+In these cases, only the specific thread identified by the handle
+.Fa L
+must be stopped through a call to
+.Xr Lstop 3PROC
+or similar.
+Note, a suspended thread
+.Po
+.Xr thr_suspend 3C
+.Pc
+is not considered stopped for these purposes.
+.El
 .Sh RETURN VALUES
 Upon successful completion, the
 .Fn Plwp_getregs ,
 .Fn Plwp_setregs ,
 .Fn Plwp_getfpregs ,
+.Fn Plwp_setfpregs ,
+.Fn Lgetfpregs ,
+.Fn Lsetfpregs ,
+.Fn Lgetregs ,
 and
-.Fn Plwp_setfpregs
+.Fn Lsetregs
 functions return
 .Sy 0
 and obtain or set the register state.
@@ -118,10 +177,12 @@
 Otherwise,
 .Sy -1
 is returned,
-.Sy errno
+.Va errno
 is set to indicate the error, and the register state is not updated nor
 are the data pointers changed.
 .Sh ERRORS
+The implementations of these functions may use standard system and
+library calls that can fail.
 For a full list of possible errors also see the
 .Sy DIAGNOSTICS
 section in
@@ -145,6 +206,19 @@
 with id
 .Fa lwpid .
 .El
+The
+.Fn Lgetfpregs ,
+.Fn Lsetfpregs ,
+.Fn Lgetregs ,
+and
+.Fn Lsetregs
+will fail if:
+.Bl -tag -width Er
+.It Er EBUSY
+The thread handle
+.Fa L
+is not currently stopped.
+.El
 .Sh INTERFACE STABILITY
 .Sy Uncommitted
 .Sh MT-LEVEL
--- old/usr/src/man/man3proc/Plwp_getxregs.3proc	Wed Apr 12 20:33:25 2023
+++ new/usr/src/man/man3proc/Plwp_getxregs.3proc	Wed Apr 12 20:33:25 2023
@@ -10,13 +10,17 @@
 .\"
 .\"
 .\" Copyright 2015 Joyent, Inc.
+.\" Copyright 2023 Oxide Computer Company
 .\"
-.Dd May 11, 2016
+.Dd January 19, 2023
 .Dt PLWP_GETXREGS 3PROC
 .Os
 .Sh NAME
 .Nm Plwp_getxregs ,
-.Nm Plwp_setxregs
+.Nm Plwp_freexregs ,
+.Nm Plwp_setxregs ,
+.Nm Lgetxregs ,
+.Nm Lsetxregs
 .Nd get and set extended register state
 .Sh LIBRARY
 .Lb libproc
@@ -26,7 +30,15 @@
 .Fo Plwp_getxregs
 .Fa "struct ps_prochandle *P"
 .Fa "lwpid_t lwpid"
+.Fa "prxregset_t **xregsp"
+.Fa "size_t *sizep"
+.Fc
+.Ft int
+.Fo Plwp_freexregs
+.Fa "struct ps_prochandle *P"
+.Fa "lwpid_t lwpid"
 .Fa "prxregset_t *xregs"
+.Fa "size_t size"
 .Fc
 .Ft int
 .Fo Plwp_setxregs
@@ -34,6 +46,17 @@
 .Fa "lwpid_t lwpid"
 .Fa "const prxregset_t *xregs"
 .Fc
+.Ft int
+.Fo Lgetxregs
+.Fa "struct ps_lwphandle *L"
+.Fa "prxregset_t **xregsp"
+.Fa "size_t *sizep"
+.Fc
+.Ft int
+.Fo Lsetxregs
+.Fa "struct ps_lwphandle *L"
+.Fa "const prxregset_t *xregs"
+.Fc
 .Sh DESCRIPTION
 The
 .Fn Plwp_getxregs
@@ -46,17 +69,45 @@
 .Pp
 The extended register state is defined by the architecture.
 These registers may refer to optional registers that have become common on the
-platform, but are not part of the standard ABI and thus not covered by
+platform, but are not part of the traditional ABI and thus not covered by
 functions such as
 .Xr Plwp_getregs 3PROC
 and
 .Xr Plwp_getfpregs 3PROC .
+Support for the extended registers varies based on the architecture and
+varies based on the specific system.
+For example, on x86 based CPUs the xregs functionality requires the x86
+xsave functionality to be supported.
 .Pp
 The
 .Fn Plwp_getxregs
-function gets the extended register state information and places it into
-.Fa xregs .
-Where as the
+function gets the extended register state information, allocates the
+appropriate sized buffer for it, and places a pointer to that buffer
+into
+.Fa xregs
+and updates
+.Fa sizep
+to indicate the size of the allocated structure.
+Due to the fact that hardware has varying sized extended register sets,
+the exact size of the structure can vary between different running
+systems and core files.
+It is also possible that some processor extensions are enabled at
+process runtime, leading for this to further be different between
+different processes on the same system.
+As such, all of the different platform instantiations of the
+.Vt prxregset_t
+end up generally being opaque structures that can be cast to something
+more specific that also includes its size.
+.Pp
+The obtained
+.Vt prxregset_t
+in
+.Fa xregsp
+must be freed by the caller with the
+.Fn Plwp_freexregs
+function.
+.Pp
+The
 .Fn Plwp_setxregs
 function sets the extended register state information in
 .Fa xregs
@@ -75,6 +126,24 @@
 .Pp
 One may not set the register values of a process that is not an active
 process, e.g. a process handle that refers to a file or a core file.
+.Pp
+The
+.Fn Lgetxregs
+and
+.Fn Lsetxregs
+functions are equivalent to the
+.Fn Plwp_getxregs
+and
+.Fn Plwp_setxregs
+functions except that they operate on a specific thread identified by
+the handle
+.Fa L
+and only require that the thread in question be stopped.
+Data allocated with the
+.Fn Lgetxregs
+functions should be freed with the
+.Fn Plwp_freexregs
+function.
 .Sh RETURN VALUES
 Upon successful completion, the
 .Fn Plwp_getxregs
@@ -104,29 +173,33 @@
 The process handle
 .Fa P
 does not have any extended register state information.
+This generally happens because the platform does not support it.
 .It Er EBUSY
 The process handle
 .Fa P
 refers to a live process and it is not stopped.
 .It Er ENOENT
-The process handle
-.Fa P
-refers to a live process and there is no thread with id
-.Fa lwpid .
-.It Er EINVAL
-The process handle
-.Fa P
-refers to a core file and there is no thread with id
-.Fa lwpid .
+There is no thread with id
+.Fa lwpid
+in the process handle
+.Fa P .
 .El
-.Sh ARCHITECTURE
 The
-.Fn Plwp_getxregs
+.Fn Lgetxregs
 and
-.Fn Plwp_setxregs
-functions are only available on
-.Sy SPARC
-platforms.
+.Fn Lsetxregs
+function will fail if:
+.Bl -tag -width Er
+.It Er ENODATA
+The thread handle
+.Fa L
+does not have any extended register state information.
+This generally happens because the platform does not support it.
+.It Er EBUSY
+The thread handle
+.Fa L
+refers to a live process and the thread is not stopped.
+.El
 .Sh INTERFACE STABILITY
 .Sy Uncommitted
 .Sh MT-LEVEL
--- old/usr/src/man/man3proc/proc_service.3proc	Wed Apr 12 20:33:26 2023
+++ new/usr/src/man/man3proc/proc_service.3proc	Wed Apr 12 20:33:26 2023
@@ -3,11 +3,10 @@
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH PROC_SERVICE 3PROC "Oct 12, 2007"
+.TH PROC_SERVICE 3PROC "Jan 23, 2023"
 .SH NAME
 proc_service \- process service interfaces
 .SH SYNOPSIS
-.LP
 .nf
 #include <proc_service.h>
 
@@ -131,7 +130,6 @@
 \fBvoid\fR \fBps_plog\fR(\fBconst char *\fR\fIfmt\fR);
 .fi
 
-.SS "SPARC"
 .LP
 .nf
 \fBps_err_e\fR \fBps_lgetxregsize\fR(\fBstruct ps_prochandle *\fR\fIph\fR,
@@ -151,7 +149,6 @@
 .fi
 
 .SS "x86"
-.LP
 .nf
 \fBps_err_e\fR \fBps_lgetLDT\fR(\fBstruct ps_prochandle *\fR\fIph\fR, \fBlwpid_t\fR \fIlwpid\fR,
      \fBstruct ssd *\fR\fIldt\fR);
@@ -158,7 +155,6 @@
 .fi
 
 .SH DESCRIPTION
-.LP
 Every program that links \fBlibthread_db\fR or \fBlibrtld_db\fR must provide a
 set of process control primitives that allow \fBlibthread_db\fR and
 \fBlibrtld_db\fR to access memory and registers in the target process, to start
@@ -363,7 +359,6 @@
 Logs a message.
 .RE
 
-.SS "SPARC"
 .ne 2
 .na
 \fB\fBps_lgetxregsize()\fR\fR
@@ -400,7 +395,6 @@
 .RE
 
 .SH ATTRIBUTES
-.LP
 See \fBattributes\fR(7) for description of the following attributes:
 .sp
 
@@ -415,7 +409,6 @@
 .TE
 
 .SH SEE ALSO
-.LP
 .BR rtld_db (3EXT),
 .BR libc_db (3LIB),
 .BR librtld_db (3LIB),
--- old/usr/src/man/man3proc/ps_lgetregs.3proc	Wed Apr 12 20:33:26 2023
+++ new/usr/src/man/man3proc/ps_lgetregs.3proc	Wed Apr 12 20:33:26 2023
@@ -3,7 +3,8 @@
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH PS_LGETREGS 3PROC "September 12, 2020"
+.\" Copyright 2023 Oxide Computer Company
+.TH PS_LGETREGS 3PROC "January 23, 2023"
 .SH NAME
 ps_lgetregs, ps_lsetregs, ps_lgetfpregs, ps_lsetfpregs, ps_lgetxregsize,
 ps_lgetxregs, ps_lsetxregs \- routines that access the target process register
@@ -60,13 +61,18 @@
 \fBps_lgetregs()\fR gets the general registers of the \fBLWP\fR identified by
 \fIlid\fR, and \fBps_lsetregs()\fR sets them. \fBps_lgetfpregs()\fR gets the
 \fBLWP\fR's floating point register set, while \fBps_lsetfpregs()\fR sets it.
-.SS "SPARC Only"
-\fBps_lgetxregsize()\fR, \fBps_lgetxregs()\fR, and \fBps_lsetxregs()\fR are
-SPARC-specific. They do not need to be defined by a controlling process on
-non-SPARC architecture. \fBps_lgetxregsize()\fR returns in
-\fB*\fR\fIxregsize\fR the size of the architecture-dependent extra state
-registers. \fBps_lgetxregs()\fR gets the extra state registers, and
-\fBps_lsetxregs()\fR sets them.
+.LP
+\fBps_lgetxregsize()\fR, \fBps_lgetxregs()\fR, and \fBps_lsetxregs()\fR are used
+to get and set the extended register set. Support for an extended register set
+depends on the instruction-set archicture. On platforms without support for
+these, controlling processes wlil still need to define them; however, they are
+allowed to simply return errors.
+.LP
+\fBps_lgetxregsize()\fR returns in \fB*\fR\fIxregsize\fR the size of the
+architecture-dependent extra state registers. \fBps_lgetxregs()\fR gets the
+extra state registers, and \fBps_lsetxregs()\fR sets them. When getting the
+registers, the size of \fIxregset\fR must be at least the size returned by
+\fBps_lgetxregsize()\fR.
 .SH RETURN VALUES
 .ne 2
 .na
@@ -79,7 +85,7 @@
 .sp
 .ne 2
 .na
-\fB\fBPS_NOFPREGS\fR \fR
+\fB\fBPS_NOFREGS\fR \fR
 .ad
 .RS 16n
 Floating point registers are neither available for this architecture nor for
--- old/usr/src/man/man5/core.5	Wed Apr 12 20:33:26 2023
+++ new/usr/src/man/man5/core.5	Wed Apr 12 20:33:26 2023
@@ -3,7 +3,7 @@
 .\" Copyright 2012 DEY Storage Systems, Inc.  All rights reserved.
 .\" Copyright (c) 2013, Joyent, Inc. All rights reserved.
 .\" Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
-.\" Copyright 2021 Oxide Computer Company
+.\" Copyright 2023 Oxide Computer Company
 .\" Copyright 1989 AT&T
 .\"
 .\" The contents of this file are subject to the terms of the
@@ -21,7 +21,7 @@
 .\" fields enclosed by brackets "[]" replaced with your own identifying
 .\" information: Portions Copyright [yyyy] [name of copyright owner]
 .\"
-.Dd August 3, 2021
+.Dd January 24, 2023
 .Dt CORE 5
 .Os
 .Sh NAME
@@ -193,7 +193,7 @@
 It contains structures defined in
 .In sys/old_procfs.h .
 New programs should recognize and skip this
-.Sy BNOTE
+.Sy NOTE
 segment, advancing instead to the new
 .Sy NOTE
 segment.
@@ -261,7 +261,7 @@
 .Fa n_type :
 .Dv NT_PRFPREG .
 This entry is present only if the
-.Sy BLWP
+.Sy LWP
 used the floating-point hardware.
 It contains the floating-point registers.
 The
@@ -281,13 +281,12 @@
 .It Vt prxregset_t
 .Fa n_type :
 .Dv NT_PRXREG .
-This entry is present only if the machine has extra register state associated
-with it.
-It contains the extra register state.
-The
+This entry is no longer included in core files, but is of historical note
+because in the past it was included on SPARC-based systems.
+While since then the
 .Vt prxregset_t
-structure is defined in
-.Vt sys/procfs_isa.h .
+and extended register sets have been defined on other architectures, they do not
+emit this in the old note section because there is no binary compatibility.
 .El
 .Pp
 The new
@@ -521,7 +520,16 @@
 The
 .Vt prxregset_t
 structure is defined in
-.In sys/procfs_isa.h .
+.In sys/procfs_isa.h ;
+however applications should include
+.In procfs.h
+to get access to it.
+On most architectures the
+.Vt prxregset_t
+is opaque and is made up of multiple structures because it can vary in length.
+.Xr proc 5
+discusses the structure of the extended register set for each supported
+architecture.
 .It Vt asrset_t
 \fB\fBasrset_t\fR\fR
 .Fa n_type :
--- old/usr/src/man/man5/proc.5	Wed Apr 12 20:33:26 2023
+++ new/usr/src/man/man5/proc.5	Wed Apr 12 20:33:26 2023
@@ -2,6 +2,7 @@
 .\" Copyright (c) 2006, Sun Microsystems, Inc. All Rights Reserved.
 .\" Copyright 2019, Joyent, Inc.
 .\" Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+.\" Copyright 2023 Oxide computer Company
 .\"
 .\" The contents of this file are subject to the terms of the
 .\" Common Development and Distribution License (the "License").
@@ -1896,6 +1897,301 @@
 It is not possible to activate an active template descriptor.
 See
 .Xr contract 5 .
+.Sh ARCHITECTURE-SPECIFIC STRUCTURES
+.Ss x86
+The x86
+.Vt prxregset_t
+structure is opaque and is made up of several different components due
+to the fact that different x86 processors enumerate different
+architectural extensions.
+.Pp
+The structure begins with a header, the
+.Vt prxregset_hdr_t ,
+which is followed by a number of different information sections which
+describe different possible extended registers.
+Each of those is covered by a
+.Vt prxregset_info_t ,
+and then finally there are different data payloads that represent each
+extended register.
+.Pp
+The number of different informational entries varies from system to
+system based on the set of architectural features that the system
+supports and the corresponding OS enablement for them.
+This structure is built around the idea of the x86
+.Sy xsave
+structure.
+That is, there is a central header which describes a bit-vector of what
+extended features are present and have valid state.
+.Pp
+Each x86 xregs file begins with the
+.Vt prxregset_hdr_t
+which looks like:
+.Bd -literal -offset 2
+typedef struct prxregset_hdr {
+	uint32_t	pr_type;
+	uint32_t	pr_size;
+	uint32_t	pr_flags;
+	uint32_t	pr_pad[4];
+	uint32_t	pr_ninfo;
+	prxregset_info_t pr_info[];
+} prxregset_hdr_t;
+.Ed
+.Pp
+The
+.Fa pr_type
+member is always set to
+.Dv PR_TYPE_XSAVE .
+This is used to indicate the type of file that is present.
+There may be different file types in the future on x86 so this value
+should always be checked.
+If it is not
+.Dv PR_TYPE_XSAVE
+then the rest of the structure may look different.
+The
+.Fa pr_size
+member indicates the size in bytes of the overall structure.
+The
+.Fa pr_flags
+and
+.Fa pr_pad
+values are currently reserved for future use.
+They will be set to zero right now when read and must be set to zero when
+writing the data.
+The
+.Fa pr_ninfo
+member indicates the number of informational items are present in
+.Fa pr_info.
+There will be one informational item for each register set that exists.
+.Pp
+The
+.Fa pr_info
+member points to an array of informational members.
+These immediately follow the structure, though the
+.Fa pr_info
+member may not be available directly if not in an environment compatible with
+some C99 features.
+Each
+.Vt prxregset_info_t
+structure looks like:
+.Bd -literal -offset 2
+typedef struct prxregset_info {
+	uint32_t pri_type;
+	uint32_t pri_flags;
+	uint32_t pri_size;
+	uint32_t pri_offset;
+} prxregset_info_t;
+.Ed
+.Pp
+The
+.Fa pri_type
+member is used to indicate the type of data and its format that this represents.
+Types are listed below.
+The
+.Fa pri_flags
+member is used to indicate future extensions or information about these items.
+Right now, these are all zero.
+The
+.Fa pri_size
+member indicates the size in bytes of the type's data.
+The
+.Fa pri_offset
+member indicates the offset to the start of the data section from the beginning
+of the xregs file.
+That is an offset of 0 would be the first byte of the
+.Vt prxregset_hdr_t .
+.Pp
+The following types of structures and their corresponding data structures are
+currently defined:
+.Bl -tag -width Ds
+.It Dv PRX_INFO_XCR - Vt prxregset_xcr_t
+This structure provides read-only access to understanding the CPU's settings for
+this thread.
+In particular, it lets you see what is set in the x86 %xcr0 register which is
+the extended feature control register and controls what extended features the
+CPU actually uses.
+It also contains the x86 extended feature disable MSR which controls features
+that are ignored.
+The
+.Vt prxregset_xcr_t
+looks like:
+.Bd -literal -offset -indent
+typedef struct prxregset_xcr {
+	uint64_t	prx_xcr_xcr0;
+	uint64_t	prx_xcr_xfd;
+	uint64_t	prx_xcr_pad[2];
+} prxregset_xcr_t;
+.Ed
+.Pp
+When setting the xregs, this entry can be left out.
+If it is included, it must match the existing entries, otherwise an error will
+be generated.
+.It Dv PRX_INFO_XSAVE - Vt prxregset_xsave_t
+This structure represents the same as the actual Intel xsave structure, which
+has both the traditional XMM state that comes from the fxsave instruction and
+then also contains the xsave header itself.
+The structure varies between 32-bit and 64-bit applications.
+The structure itself looks like:
+.Bd -literal
+typedef struct prxregset_xsave {
+	uint16_t	prx_fx_fcw;
+	uint16_t	prx_fx_fsw;
+	uint16_t	prx_fx_fctw;	/* compressed tag word */
+	uint16_t	prx_fx_fop;
+#if defined(__amd64)
+	uint64_t	prx_fx_rip;
+	uint64_t	prx_fx_rdp;
+#else
+	uint32_t	prx_fx_eip;
+	uint16_t	prx_fx_cs;
+	uint16_t	__prx_fx_ign0;
+	uint32_t	prx_fx_dp;
+	uint16_t	prx_fx_ds;
+	uint16_t	__prx_fx_ign1;
+#endif
+	uint32_t	prx_fx_mxcsr;
+	uint32_t	prx_fx_mxcsr_mask;
+	union {
+		uint16_t prx_fpr_16[5];	/* 80-bits of x87 state */
+		u_longlong_t prx_fpr_mmx;	/* 64-bit mmx register */
+		uint32_t _prx__fpr_pad[4];	/* (pad out to 128-bits) */
+	} fx_st[8];
+#if defined(__amd64)
+	upad128_t	prx_fx_xmm[16];	/* 128-bit registers */
+	upad128_t	__prx_fx_ign2[6];
+#else
+	upad128_t	prx_fx_xmm[8];	/* 128-bit registers */
+	upad128_t	__prx_fx_ign2[14];
+#endif
+	uint64_t	prx_xsh_xstate_bv;
+	uint64_t	prx_xsh_xcomp_bv;
+	uint64_t	prx_xsh_reserved[6];
+} prxregset_xsave_t;
+.Ed
+.Pp
+In the classical fxsave portion of the structure, most of the members follow the
+same meaning and match their presence in the fpregs file and their use as
+discussed in the Intel and AMD software developer manuals.
+The one exception is that when setting the
+.Fa prx_fx_mxcsr
+member reserved bits that are set will be masked off and ignored.
+.Pp
+The most notable fields to consider here right now are the last few members
+which are part of the xsave header itself.
+In particular, the
+.Fa prx_xsh_xstate_bv
+component is used to track the actual features whose content are valid.
+When reading the registers, if a given entry is not valid, the register state
+will write out the informational entry in its default state.
+When setting the extended registers, this notes which features will be loaded
+from their default state
+.Pq as defined by Intel and AMD's manuals
+and which will be loaded from the informational entries.
+If a bit is set in the
+.Fa prx_xsh_xstate_bv
+entry, then it must be present as its own informational entry otherwise a write
+will fail.
+If an informational entry is present in a write, but not set in the
+.Fa prx_xsh_xstate_bv
+then its contents will be ignored.
+.Pp
+The xregs format currently does not support any compressed items being specified
+nor does it specify any, so the
+.Fa prx_xsh_xcomp_bv
+member will be always set to zero and it and the reserved members
+.Fa prx_xsh_reserved
+must all be left as zero.
+.It Dv PRX_INFO_YMM - Vt prxregset_ymm_t
+This structure contains the upper 128-bits of the first 16 %ymm registers
+.Pq 8 for 32-bit applications .
+To construct a full vector register, it must be combined with the
+.Fa prx_fx_xmm
+member of the
+.Dv PRX_INFO_XSAVE
+data.
+In 32-bit applications, the reserved registers must be written as zero.
+The structure itself looks like:
+.Bd -literal -offset 2
+typedef struct prxregset_ymm {
+#if defined(__amd64)
+	upad128_t	prx_ymm[16];
+#else
+	upad128_t	prx_ymm[8];
+	upad128_t	prx_rsvd[8];
+#endif
+} prxregset_ymm_t;
+.Ed
+.It Dv PRX_INFO_OPMASK - Vt prxregset_opmask_t
+This structure represents one portion of Intel's AVX-512 state: the 8 64-bit
+mask registers, %k0 through %k7.
+The structure looks like:
+.Bd -literal -offset 2
+typedef struct prxregset_opmask {
+	uint64_t	prx_opmask[8];
+} prxregset_opmask_t;
+.Ed
+.It Dv PRX_INFO_ZMM - Vt prxregset_zmm_t
+This structure represents one portion of Intel's AVX-512 state: the upper
+256 bits of the 512-bit %zmm0 through %zmm15 registers.
+Bits 0-127 are found in the
+.Fa prx_fx_xmm
+member of the
+.Dv PRX_INFO_XSAVE
+data and bits 128-255 are found in the
+.Fa prx_ymm
+member of the
+.Dv PRX_INFO_YMM .
+32-bit applications only have access to %zmm0 through %zmm7.
+This structure looks like:
+.Bd -literal -offset 2
+typedef struct prxregset_zmm {
+#if defined(__amd64)
+	upad256_t	prx_zmm[16];
+#else
+	upad256_t	prx_zmm[8];
+	upad256_t	prx_rsvd[8];
+#endif
+} prxregset_zmm_t;
+.Ed
+.It Dv PRX_INFO_HI_ZMM - Vt prxregset_hi_zmm_t
+This structure represents the third portion of Intel's AVX-512 state: the
+additional 16 512-bit registers that are available to 64-bit applications, but
+not 32-bit applications.
+This represents %zmm16 through %zmm31.
+This structure looks like:
+.Bd -literal -offset indent
+typedef struct prxregset_hi_zmm {
+#if defined(__amd64)
+	upad512_t	prx_hi_zmm[16];
+#else
+	upad512_t	prx_rsvd[16];
+#endif
+} prxregset_hi_zmm_t;
+.Pp
+Unlike the other lower %zmm registers of %zmm0 through %zmm15, this contains the
+entire 512-bit register in one spot and there is no need to look at other
+information items to reconstitute the entire vector.
+.Ed
+.Pp
+When setting the extended registers, at least the
+.Dv PRX_INFO_XSAVE
+component must be present.
+None of the component offsets may overlap with the
+.Vt prxregset_hdr_t
+or any of the
+.Vt prxregset_info_t
+structures.
+In the written data file, it is expected that the various structures start with
+their naturally expected alignment, which is most often 16 bytes
+.Po
+that is the value that the C
+.Fn alignof
+keyword will return
+.Pc .
+The structures that we use are all multiples of 16 bytes to make this easier.
+The kernel will write out structures with a greater alignment such that the
+portions of registers are aligned and safely usable with instructions that move
+aligned integers such as vmovdqu64.
+.El
 .Sh CONTROL MESSAGES
 Process state changes are effected through messages written to a process's
 .Sy ctl
@@ -1938,7 +2234,7 @@
 .Sy PCDSTOP
 directs the specific lwp to stop without waiting for it to stop, and
 .Sy PCWSTOP
- simply waits for the specific lwp to stop.
+simply waits for the specific lwp to stop.
 When applied to an lwp control file,
 .Sy PCSTOP
 and
@@ -1973,7 +2269,7 @@
 and
 .Sy PCSEXIT ) .
 .Sy PR_JOBCONTROL
- and
+and
 .Sy PR_SUSPENDED
 stops are specifically not events of interest.
 (An lwp may stop twice due to a stop signal, first showing
@@ -2587,7 +2883,8 @@
 structure.
 An error
 .Pq Er EINVAL
-is returned if the system does not support extra state registers.
+is returned if the system does not support extra state registers or the register
+state is invalid.
 .Sy PCSXREG
 fails with
 .Er EBUSY
--- old/usr/src/pkg/manifests/system-header.p5m	Wed Apr 12 20:33:27 2023
+++ new/usr/src/pkg/manifests/system-header.p5m	Wed Apr 12 20:33:27 2023
@@ -29,7 +29,7 @@
 # Copyright 2020 Joyent, Inc.
 # Copyright 2019 Peter Tribble.
 # Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
-# Copyright 2022 Oxide Computer company
+# Copyright 2023 Oxide Computer company
 #
 
 set name=pkg.fmri value=pkg:/system/header@$(PKGVERS)
@@ -1353,6 +1353,7 @@
 file path=usr/include/sys/stat_impl.h
 file path=usr/include/sys/statfs.h
 file path=usr/include/sys/statvfs.h
+file path=usr/include/sys/stdalign.h
 file path=usr/include/sys/stdbool.h
 file path=usr/include/sys/stddef.h
 file path=usr/include/sys/stdint.h
--- old/usr/src/pkg/manifests/system-kernel.man2.inc	Wed Apr 12 20:33:27 2023
+++ new/usr/src/pkg/manifests/system-kernel.man2.inc	Wed Apr 12 20:33:27 2023
@@ -88,6 +88,7 @@
 link path=usr/share/man/man2/getaudit_addr.2 target=getaudit.2
 file path=usr/share/man/man2/getauid.2
 file path=usr/share/man/man2/getcontext.2
+link path=usr/share/man/man2/getcontext_extd.2 target=getcontext.2
 file path=usr/share/man/man2/getdents.2
 link path=usr/share/man/man2/getegid.2 target=getuid.2
 link path=usr/share/man/man2/geteuid.2 target=getuid.2
--- old/usr/src/pkg/manifests/system-library.man3c.inc	Wed Apr 12 20:33:27 2023
+++ new/usr/src/pkg/manifests/system-library.man3c.inc	Wed Apr 12 20:33:27 2023
@@ -1264,6 +1264,7 @@
 link path=usr/share/man/man3c/strxfrm_l.3c target=strxfrm.3c
 file path=usr/share/man/man3c/swab.3c
 link path=usr/share/man/man3c/swapcontext.3c target=makecontext.3c
+link path=usr/share/man/man3c/swapcontext_extd.3c target=makecontext.3c
 link path=usr/share/man/man3c/swprintf.3c target=fwprintf.3c
 link path=usr/share/man/man3c/swscanf.3c target=fwscanf.3c
 file path=usr/share/man/man3c/sync_instruction_memory.3c
@@ -1361,6 +1362,8 @@
 file path=usr/share/man/man3c/u8_textprep_str.3c
 file path=usr/share/man/man3c/u8_validate.3c
 file path=usr/share/man/man3c/ualarm.3c
+file path=usr/share/man/man3c/ucontext_alloc.3c
+link path=usr/share/man/man3c/ucontext_free.3c target=ucontext_alloc.3c
 file path=usr/share/man/man3c/uconv_u16tou32.3c
 link path=usr/share/man/man3c/uconv_u16tou8.3c target=uconv_u16tou32.3c
 link path=usr/share/man/man3c/uconv_u32tou16.3c target=uconv_u16tou32.3c
--- old/usr/src/pkg/manifests/system-library.man3proc.inc	Wed Apr 12 20:33:27 2023
+++ new/usr/src/pkg/manifests/system-library.man3proc.inc	Wed Apr 12 20:33:27 2023
@@ -22,6 +22,9 @@
 link path=usr/share/man/man3proc/Ldstop.3proc target=Pstopstatus.3proc
 file path=usr/share/man/man3proc/Lfree.3proc
 link path=usr/share/man/man3proc/Lgetareg.3proc target=Pgetareg.3proc
+link path=usr/share/man/man3proc/Lgetfpregs.3proc target=Plwp_getregs.3proc
+link path=usr/share/man/man3proc/Lgetregs.3proc target=Plwp_getregs.3proc
+link path=usr/share/man/man3proc/Lgetxregs.3proc target=Plwp_getxregs.3proc
 file path=usr/share/man/man3proc/Lgrab.3proc
 file path=usr/share/man/man3proc/Lgrab_error.3proc
 link path=usr/share/man/man3proc/Lmain_stack.3proc target=Plwp_stack.3proc
@@ -28,7 +31,10 @@
 file path=usr/share/man/man3proc/Lprochandle.3proc
 file path=usr/share/man/man3proc/Lpsinfo.3proc
 link path=usr/share/man/man3proc/Lputareg.3proc target=Pgetareg.3proc
+link path=usr/share/man/man3proc/Lsetfpregs.3proc target=Plwp_getregs.3proc
+link path=usr/share/man/man3proc/Lsetregs.3proc target=Plwp_getregs.3proc
 link path=usr/share/man/man3proc/Lsetrun.3proc target=Psetrun.3proc
+link path=usr/share/man/man3proc/Lsetxregs.3proc target=Plwp_getxregs.3proc
 link path=usr/share/man/man3proc/Lstack.3proc target=Plwp_stack.3proc
 file path=usr/share/man/man3proc/Lstate.3proc
 file path=usr/share/man/man3proc/Lstatus.3proc
@@ -86,6 +92,7 @@
 link path=usr/share/man/man3proc/Plookup_by_name.3proc \
     target=Plookup_by_addr.3proc
 link path=usr/share/man/man3proc/Plwp_alt_stack.3proc target=Plwp_stack.3proc
+link path=usr/share/man/man3proc/Plwp_freexregs.3proc target=Plwp_getxregs.3proc
 file path=usr/share/man/man3proc/Plwp_getasrs.3proc
 link path=usr/share/man/man3proc/Plwp_getfpregs.3proc target=Plwp_getregs.3proc
 file path=usr/share/man/man3proc/Plwp_getgwindows.3proc
--- old/usr/src/pkg/manifests/system-test-ostest.p5m	Wed Apr 12 20:33:27 2023
+++ new/usr/src/pkg/manifests/system-test-ostest.p5m	Wed Apr 12 20:33:27 2023
@@ -66,6 +66,8 @@
 file path=opt/os-tests/tests/file-locking/acquire-lock.64 mode=0555
 file path=opt/os-tests/tests/file-locking/runtests.32 mode=0555
 file path=opt/os-tests/tests/file-locking/runtests.64 mode=0555
+file path=opt/os-tests/tests/getcontext_extd.32 mode=0555
+file path=opt/os-tests/tests/getcontext_extd.64 mode=0555
 $(i386_ONLY)dir path=opt/os-tests/tests/i386
 $(i386_ONLY)file path=opt/os-tests/tests/i386/badseg mode=0555
 $(i386_ONLY)file path=opt/os-tests/tests/i386/badseg_exec mode=0555
@@ -176,6 +178,57 @@
 file path=opt/os-tests/tests/ucontext.64 mode=0555
 file path=opt/os-tests/tests/writev.32 mode=0555
 file path=opt/os-tests/tests/writev.64 mode=0555
+$(i386_ONLY)dir path=opt/os-tests/tests/xsave
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/bad_xregs.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/bad_xregs.64 mode=0555
+$(i386_ONLY)dir path=opt/os-tests/tests/xsave/data
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/mdb_xregs.ymm.32 mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/mdb_xregs.ymm.64 mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/mdb_xregs.zmm.32 mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/mdb_xregs.zmm.64 mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.fpregs.ymm.32 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.fpregs.ymm.64 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.fpregs.zmm.32 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.fpregs.zmm.64 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.xregs.ymm.32 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.xregs.ymm.64 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.xregs.zmm.32 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/data/proc_writes.xregs.zmm.64 \
+    mode=0444
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/fpregs.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/fpregs.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/fpregs_xbv.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/fpregs_xbv.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/mdb_xregs mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/proc_writes mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/proc_xregs_set.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/proc_xregs_set.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/setcontext_replace.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/setcontext_replace.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/setcontext_restore.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/setcontext_restore.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/signal_restore.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/signal_restore.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/swapcontext_extd.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/swapcontext_extd.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xregs_dump.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xregs_dump.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xregs_roundtrip.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xregs_roundtrip.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xregs_set.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xregs_set.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xsave_baducontext mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xsave_baducontext.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xsave_baducontext.64 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xsu_hwtype.32 mode=0555
+$(i386_ONLY)file path=opt/os-tests/tests/xsave/xsu_hwtype.64 mode=0555
 file path=opt/os-tests/tests/zen_umc_test mode=0555
 license cr_Sun license=cr_Sun
 license lic_CDDL license=lic_CDDL
--- old/usr/src/test/os-tests/runfiles/default.run	Wed Apr 12 20:33:27 2023
+++ new/usr/src/test/os-tests/runfiles/default.run	Wed Apr 12 20:33:27 2023
@@ -41,6 +41,8 @@
 
 [/opt/os-tests/tests/ucontext.32]
 [/opt/os-tests/tests/ucontext.64]
+[/opt/os-tests/tests/getcontext_extd.32]
+[/opt/os-tests/tests/getcontext_extd.64]
 
 [/opt/os-tests/tests/secflags]
 user = root
@@ -159,3 +161,23 @@
 	'illumos-15031',
 	'illumos-15036'
 	]
+
+#
+# The following tests all represent tests that require the xsave feature
+# set. These are all x86 specific. The use of arch=i86pc below doesn't
+# really represent things that can be run on x86 regardless of the
+# platform and when the test runner gains support for specifying that,
+# use that instead. This would be split into two groups, but that isn't
+# supported, so everything ends up being run as root for the sake of the
+# xsave_baducontext test.
+#
+[/opt/os-tests/tests/xsave]
+arch = i86pc
+pre = xsu_hwtype.64
+user = root
+tests = [ 'bad_xregs.32', 'bad_xregs.64', 'mdb_xregs', 'proc_writes',
+    'setcontext_replace.32', 'setcontext_replace.64', 'setcontext_restore.32',
+    'setcontext_restore.64', 'signal_restore.32', 'signal_restore.64',
+    'xregs_roundtrip.32', 'xregs_roundtrip.64', 'xsave_baducontext',
+    'fpregs_xbv.32', 'fpregs_xbv.64', 'swapcontext_extd.32',
+    'swapcontext_extd.64' ]
--- old/usr/src/test/os-tests/tests/Makefile	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/Makefile	Wed Apr 12 20:33:28 2023
@@ -17,7 +17,7 @@
 # Copyright 2023 Oxide Computer Company
 #
 
-SUBDIRS_i386 = i386 imc zen_umc
+SUBDIRS_i386 = i386 imc zen_umc xsave
 
 SUBDIRS =       \
 		cores \
@@ -50,6 +50,7 @@
 	odirectory \
 	OS-6097 \
 	ucontext \
+	getcontext_extd \
 	writev
 
 CPPFLAGS += -D_REENTRANT
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/getcontext_extd.c	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,117 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * Basic test for getcontext_extd() variant. We have two cases that we need to
+ * consider. A ucontext_t that is allocated and initialized from the stack and
+ * then one that comes from a ucontext_alloc(). We test both that setcontext()
+ * and makecontext() work with these.
+ */
+
+#include <ucontext.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+#include <errno.h>
+#include <upanic.h>
+
+#define	STACK_MAGIC	42
+#define	EXIT_MAGIC	23
+static volatile uint32_t count = 0;
+static volatile uint32_t stack_count = 0;
+
+static void
+successful_exit(uint32_t test)
+{
+	if (test != EXIT_MAGIC) {
+		errx(EXIT_FAILURE, "TEST FAILED: makecontext had wrong "
+		    "argument, found 0x%x, expected 0x%x", test, EXIT_MAGIC);
+	}
+
+	printf("TEST PASSED: makecontext called with right argument\n");
+	exit(0);
+}
+
+static void
+getcontext_stack(uint32_t test)
+{
+	ucontext_t ctx;
+
+	if (test != STACK_MAGIC) {
+		errx(EXIT_FAILURE, "TEST FAILED: makecontext had wrong "
+		    "argument, found 0x%x, expected 0x%x", test, STACK_MAGIC);
+	}
+
+	(void) memset(&ctx, 0, sizeof (ctx));
+
+	if (getcontext_extd(&ctx, 0) != 0) {
+		err(EXIT_FAILURE, "failed to get extended context from stack");
+	}
+
+	count++;
+	if (count < 5) {
+		const char *msg = "stack setcontext returned, sorry";
+		(void) setcontext(&ctx);
+		upanic(msg, strlen(msg) + 1);
+	}
+
+	(void) printf("TEST PASSED: stack ucontext_t / getcontext_extd() / "
+	    "setcontext() combo worked\n");
+	makecontext(&ctx, successful_exit, 1, EXIT_MAGIC);
+	(void) setcontext(&ctx);
+
+	err(EXIT_FAILURE, "TEST FAILED: stack ucontext_t / makecontext() "
+	    "returned from setcontext()");
+}
+
+int
+main(void)
+{
+	ucontext_t *ctx = ucontext_alloc(0);
+	if (ctx == NULL) {
+		err(EXIT_FAILURE, "failed to get allocate ucontext_t");
+	}
+
+	if (getcontext_extd(ctx, 23) == 0) {
+		errx(EXIT_FAILURE, "TEST FAILED: getcontext_extd worked with "
+		    "bad flags");
+	}
+
+	if (errno != EINVAL) {
+		errx(EXIT_FAILURE, "TEST FAILED: getcontext_extd returned "
+		    "wrong errno for bad flags: 0x%x", errno);
+	}
+
+	if (getcontext_extd(ctx, 0) != 0) {
+		err(EXIT_FAILURE, "failed to get extended context");
+	}
+
+	count++;
+	if (count < 5) {
+		const char *msg = "setcontext returned, sorry";
+		(void) setcontext(ctx);
+		upanic(msg, strlen(msg) + 1);
+	}
+
+	(void) printf("TEST PASSED: ucontext_alloc() / getcontext_extd() / "
+	    "setcontext() combo worked\n");
+	makecontext(ctx, getcontext_stack, 1, STACK_MAGIC);
+	(void) setcontext(ctx);
+
+	warn("TEST FAILED: failed to setcontext() to makecontext() from "
+	    "ucontext_alloc() / getcontext_extd()");
+	return (EXIT_FAILURE);
+}
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/Makefile	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,145 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2023 Oxide Computer Company
+#
+
+PROGS = \
+	bad_xregs \
+	fpregs \
+	fpregs_xbv \
+	proc_xregs_set \
+	setcontext_replace \
+	setcontext_restore \
+	swapcontext_extd \
+	signal_restore \
+	xregs_dump \
+	xregs_roundtrip \
+	xregs_set \
+	xsave_baducontext \
+	xsu_hwtype
+
+SCRIPTS = mdb_xregs proc_writes xsave_baducontext
+
+DATAFILES = \
+	mdb_xregs.ymm.32 \
+	mdb_xregs.ymm.64 \
+	mdb_xregs.zmm.32 \
+	mdb_xregs.zmm.64 \
+	proc_writes.fpregs.ymm.32 \
+	proc_writes.fpregs.ymm.64 \
+	proc_writes.fpregs.zmm.32 \
+	proc_writes.fpregs.zmm.64 \
+	proc_writes.xregs.ymm.32 \
+	proc_writes.xregs.ymm.64 \
+	proc_writes.xregs.zmm.32 \
+	proc_writes.xregs.zmm.64
+
+PROGS32 = $(PROGS:%=%.32)
+PROGS64 = $(PROGS:%=%.64)
+
+ROOTOPTDIR = $(ROOT)/opt/os-tests/tests
+ROOTOPTXSAVE = $(ROOTOPTDIR)/xsave
+ROOTOPTPROGS = $(PROGS32:%=$(ROOTOPTXSAVE)/%) \
+	$(PROGS64:%=$(ROOTOPTXSAVE)/%) \
+	$(SCRIPTS:%=$(ROOTOPTXSAVE)/%)
+ROOTOPTDATADIR = $(ROOTOPTXSAVE)/data
+ROOTOPTDATA = $(DATAFILES:%=$(ROOTOPTDATADIR)/%)
+
+COMMOBJS32 = xsave_util.32.o xsave_asm32.32.o
+COMMOBJS64 = xsave_util.64.o xsave_asm64.64.o
+
+OBJS32 = $(COMMOBJS32) $(PROGS:%=%.32.o)
+OBJS64 = $(COMMOBJS64) $(PROGS:%=%.64.o)
+
+#
+# To simplify things we always link in the support files to programs
+# even though in some case (e.g. xsave_baducontext) they aren't needed.
+# This also drives a requirement that everything links against libproc
+# (seen below).
+#
+EXTRA_OBJS32 = $(COMMOBJS32)
+EXTRA_OBJS64 = $(COMMOBJS64)
+
+include $(SRC)/cmd/Makefile.cmd
+
+#
+# These are required to get COMPILE.s to work as Makefile.master puts
+# some things in AS_CPPFLAGS, but not what we need to make it work.
+#
+AS_CPPFLAGS += -D_ASM
+
+$(ROOTOPTDATA) := FILEMODE = 0444
+
+LDLIBS += -lproc
+LDLIBS64 += -lproc
+
+CSTD = $(GNU_C99)
+
+.KEEP_STATE:
+
+all: $(PROGS32) $(PROGS64)
+
+install: $(ROOTOPTPROGS) $(ROOTOPTDATA)
+
+clean:
+	$(RM) $(OBJS32) $(OBJS64)
+
+$(PROGS32): $(OBJS32)
+$(PROGS64): $(OBJS64)
+
+$(ROOTOPTPROGS): $(PROGS32) $(PROGS64) $(ROOTOPTXSAVE)
+$(ROOTOPTDATA): $(ROOTOPTDATADIR)
+
+$(ROOTOPTDIR):
+	$(INS.dir)
+
+$(ROOTOPTXSAVE): $(ROOTOPTDIR)
+	$(INS.dir)
+
+$(ROOTOPTDATADIR): $(ROOTOPTXSAVE)
+	$(INS.dir)
+
+$(ROOTOPTXSAVE)/%: %
+	$(INS.file)
+
+$(ROOTOPTXSAVE)/%: %.ksh
+	$(INS.rename)
+
+%.32.o: %.c
+	$(COMPILE.c) -o $@ $<
+	$(POST_PROCESS_O)
+
+%.64.o: %.c
+	$(COMPILE64.c) -o $@ $<
+	$(POST_PROCESS_O)
+
+%.32.o: %.S
+	$(COMPILE.s) -o $@ $<
+	$(POST_PROCESS_S_0)
+
+%.64.o: %.S
+	$(COMPILE64.s) -o $@ $<
+	$(POST_PROCESS_S_0)
+
+%.64: %.64.o
+	$(LINK64.c) -o $@ $< $(LDLIBS64) $(EXTRA_OBJS64)
+	$(POST_PROCESS)
+
+%.32: %.32.o
+	$(LINK.c) -o $@ $< $(LDLIBS) $(EXTRA_OBJS32)
+	$(POST_PROCESS)
+
+clobber: clean
+	$(RM) $(PROGS32) $(PROGS64)
+
+FRC:
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/bad_xregs.c	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,723 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This attempts to do a series of writes to the /proc control file that have
+ * invalid data for the xregs state. The way that this works is that we create a
+ * thread that will be detached and just sleeps whenever it wakes up. We direct
+ * this thread to stop with a directed PCSTOP via libproc.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <libproc.h>
+#include <thread.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/sysmacros.h>
+#include <sys/debug.h>
+#include <sys/x86_archext.h>
+
+#include "xsave_util.h"
+
+static prxregset_t *bad_xregs_pxr;
+static size_t bad_xregs_size;
+
+typedef struct bad_xregs_test {
+	const char *bxt_desc;
+	int bxt_errno;
+	uint32_t bxt_min;
+	void (*bxt_setup)(void **, size_t *);
+} bad_xregs_test_t;
+
+static void
+bad_xregs_no_data(void **bufp, size_t *sizep)
+{
+	*bufp = NULL;
+	*sizep = 0;
+}
+
+static void
+bad_xregs_null_buf(void **bufp, size_t *sizep)
+{
+	*bufp = NULL;
+	*sizep = sizeof (prxregset_hdr_t);
+}
+
+static void
+bad_xregs_short_hdr(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = calloc(1, sizeof (prxregset_hdr_t));
+	if (hdr == NULL) {
+		err(EXIT_FAILURE, "failed to allocate header");
+	}
+
+	hdr->pr_type = PR_TYPE_XSAVE;
+	hdr->pr_size = sizeof (prxregset_hdr_t);
+
+	*bufp = hdr;
+	*sizep = sizeof (prxregset_hdr_t) - 4;
+}
+
+static void
+bad_xregs_hdr_too_large(void **bufp, size_t *sizep)
+{
+	uint32_t large = 32 * 1024 * 1024; /* 4 MiB */
+	prxregset_hdr_t *hdr = malloc(32 * 1024 * 1024);
+	if (hdr == NULL) {
+		err(EXIT_FAILURE, "failed to allocate regset");
+	}
+
+	(void) memcpy(hdr, bad_xregs_pxr, bad_xregs_size);
+	hdr->pr_size = large;
+
+	*bufp = hdr;
+	*sizep = large;
+}
+
+static prxregset_hdr_t *
+bad_xregs_std_init(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = malloc(bad_xregs_size);
+	if (hdr == NULL) {
+		err(EXIT_FAILURE, "failed to allocate regset");
+	}
+
+	(void) memcpy(hdr, bad_xregs_pxr, bad_xregs_size);
+
+	*bufp = hdr;
+	*sizep = bad_xregs_size;
+	return (hdr);
+}
+
+static void
+bad_xregs_missing_data(void **bufp, size_t *sizep)
+{
+	(void) bad_xregs_std_init(bufp, sizep);
+	*sizep /= 2;
+}
+
+static void
+bad_xregs_hdr_bad_type(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_type = PR_TYPE_XSAVE + 167;
+}
+
+static void
+bad_xregs_hdr_bad_flags(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_flags = 0x123;
+}
+
+static void
+bad_xregs_hdr_bad_pad0(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_pad[0] = 0x456;
+}
+
+static void
+bad_xregs_hdr_bad_pad1(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_pad[1] = 0x789;
+}
+
+static void
+bad_xregs_hdr_bad_pad2(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_pad[2] = 0xabc;
+}
+
+static void
+bad_xregs_hdr_bad_pad3(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_pad[3] = 0xdef;
+}
+
+static void
+bad_xregs_hdr_no_info(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_ninfo = 0;
+}
+
+static void
+bad_xregs_hdr_no_info_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	uint32_t len = sizeof (prxregset_hdr_t) + sizeof (prxregset_info_t) *
+	    hdr->pr_ninfo;
+	hdr->pr_size = len - 4;
+}
+
+static void
+bad_xregs_info_type(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_info[0].pri_type = 0xbaddcafe;
+}
+
+static void
+bad_xregs_info_flags(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	VERIFY3U(hdr->pr_ninfo, >=, 2);
+	hdr->pr_info[1].pri_flags = 0x120b0;
+}
+
+static prxregset_info_t *
+bad_xregs_find_info(prxregset_hdr_t *hdr, uint32_t type)
+{
+	for (uint32_t i = 0; i < hdr->pr_ninfo; i++) {
+		if (hdr->pr_info[i].pri_type == type) {
+			return (&hdr->pr_info[i]);
+		}
+	}
+
+	return (NULL);
+}
+
+static void
+bad_xregs_info_xcr_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XCR);
+	VERIFY3P(info, !=, NULL);
+	info->pri_size--;
+}
+
+static void
+bad_xregs_info_xcr_off(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XCR);
+	VERIFY3P(info, !=, NULL);
+	info->pri_offset++;
+}
+
+static void
+bad_xregs_info_xsave_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	info->pri_size--;
+}
+
+static void
+bad_xregs_info_xsave_off(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	info->pri_offset--;
+}
+
+static void
+bad_xregs_info_ymm_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_YMM);
+	VERIFY3P(info, !=, NULL);
+	info->pri_size--;
+}
+
+static void
+bad_xregs_info_ymm_off(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_YMM);
+	VERIFY3P(info, !=, NULL);
+	info->pri_offset--;
+}
+
+static void
+bad_xregs_info_opmask_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_OPMASK);
+	VERIFY3P(info, !=, NULL);
+	info->pri_size--;
+}
+
+static void
+bad_xregs_info_opmask_off(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_OPMASK);
+	VERIFY3P(info, !=, NULL);
+	info->pri_offset--;
+}
+
+static void
+bad_xregs_info_zmm_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_ZMM);
+	VERIFY3P(info, !=, NULL);
+	info->pri_size--;
+}
+
+static void
+bad_xregs_info_zmm_off(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_ZMM);
+	VERIFY3P(info, !=, NULL);
+	info->pri_offset--;
+}
+
+static void
+bad_xregs_info_hi_zmm_len(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_HI_ZMM);
+	VERIFY3P(info, !=, NULL);
+	info->pri_size--;
+}
+
+static void
+bad_xregs_info_hi_zmm_off(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_HI_ZMM);
+	VERIFY3P(info, !=, NULL);
+	info->pri_offset--;
+}
+
+static void
+bad_xregs_info_exceeds_len0(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_info[0].pri_offset = hdr->pr_size + 4;
+}
+
+static void
+bad_xregs_info_exceeds_len1(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_info[0].pri_offset = hdr->pr_size - hdr->pr_info[0].pri_size +
+	    8;
+}
+
+static void
+bad_xregs_info_overlaps(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	hdr->pr_info[0].pri_offset = sizeof (prxregset_hdr_t) + 8;
+}
+
+static void
+bad_xregs_trim_entry(prxregset_hdr_t *hdr, uint32_t type)
+{
+	boolean_t found = B_FALSE;
+	/*
+	 * Walk the info structures and clip out everything after the xsave
+	 * entry. This almost suggets it'd be nice to have a nop type that was
+	 * ignored.
+	 */
+	for (uint32_t i = 0; i < hdr->pr_ninfo; i++) {
+		if (hdr->pr_info[i].pri_type == type) {
+			found = B_TRUE;
+		}
+
+		if (found && i + 1 != hdr->pr_ninfo) {
+			hdr->pr_info[i] = hdr->pr_info[i + 1];
+		}
+	}
+
+	VERIFY3U(found, ==, B_TRUE);
+	hdr->pr_ninfo--;
+}
+
+static void
+bad_xregs_no_xsave(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	bad_xregs_trim_entry(hdr, PRX_INFO_XSAVE);
+}
+
+static void
+bad_xregs_missing_xstate(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	bad_xregs_trim_entry(hdr, PRX_INFO_YMM);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+
+	xsave->prx_xsh_xstate_bv |= XFEATURE_AVX;
+}
+
+static void
+bad_xregs_xcr_bad_xcr0(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XCR);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xcr_t *xcr = (void *)((uintptr_t)*bufp + info->pri_offset);
+	xcr->prx_xcr_xcr0 = ~xcr->prx_xcr_xcr0;
+}
+
+static void
+bad_xregs_xcr_bad_xfd(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XCR);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xcr_t *xcr = (void *)((uintptr_t)*bufp + info->pri_offset);
+	xcr->prx_xcr_xfd = ~xcr->prx_xcr_xfd;
+}
+
+static void
+bad_xregs_xcr_bad_pad0(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XCR);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xcr_t *xcr = (void *)((uintptr_t)*bufp + info->pri_offset);
+	xcr->prx_xcr_pad[0] = 0xdeadbeef;
+}
+
+static void
+bad_xregs_xcr_bad_pad1(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XCR);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xcr_t *xcr = (void *)((uintptr_t)*bufp + info->pri_offset);
+	xcr->prx_xcr_pad[1] = 0xf00b412;
+}
+
+static void
+bad_xregs_xsave_bad_xbv(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	/*
+	 * bit 8 is a supervisor state that we don't currently have defined in
+	 * <sys/x86_archext.h> and should always end up being something we don't
+	 * see in userland.
+	 */
+	xsave->prx_xsh_xstate_bv |= (1 << 8);
+}
+
+static void
+bad_xregs_xsave_bad_xcomp(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	/*
+	 * bit 63 is used to say that this is valid. Given that we don't support
+	 * it, we just set that bit as the most realistic example of what could
+	 * happen.
+	 */
+	xsave->prx_xsh_xcomp_bv |= (1ULL << 63);
+}
+
+static void
+bad_xregs_xsave_bad_rsvd0(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	xsave->prx_xsh_reserved[0] = 0xff10;
+}
+
+static void
+bad_xregs_xsave_bad_rsvd1(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	xsave->prx_xsh_reserved[1] = 0x87654321;
+}
+
+static void
+bad_xregs_xsave_bad_rsvd2(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	xsave->prx_xsh_reserved[2] = 0x167169;
+}
+
+static void
+bad_xregs_xsave_bad_rsvd3(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	xsave->prx_xsh_reserved[3] = 0xff7;
+}
+
+static void
+bad_xregs_xsave_bad_rsvd4(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	xsave->prx_xsh_reserved[4] = 0x00f00;
+}
+
+static void
+bad_xregs_xsave_bad_rsvd5(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_XSAVE);
+	VERIFY3P(info, !=, NULL);
+	prxregset_xsave_t *xsave = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	xsave->prx_xsh_reserved[5] = 0x2374013;
+}
+
+/*
+ * The following tests are all 32-bit specific.
+ */
+#ifdef __i386
+static void
+bad_xregs_ymm_ilp32(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_YMM);
+	VERIFY3P(info, !=, NULL);
+	prxregset_ymm_t *ymm = (void *)((uintptr_t)*bufp + info->pri_offset);
+	ymm->prx_rsvd[4]._l[3] = 0x12345;
+}
+
+static void
+bad_xregs_zmm_ilp32(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_ZMM);
+	VERIFY3P(info, !=, NULL);
+	prxregset_zmm_t *zmm = (void *)((uintptr_t)*bufp + info->pri_offset);
+	zmm->prx_rsvd[2]._l[5] = 0x23456;
+}
+
+static void
+bad_xregs_hi_zmm_ilp32(void **bufp, size_t *sizep)
+{
+	prxregset_hdr_t *hdr = bad_xregs_std_init(bufp, sizep);
+	prxregset_info_t *info = bad_xregs_find_info(hdr, PRX_INFO_HI_ZMM);
+	VERIFY3P(info, !=, NULL);
+	prxregset_hi_zmm_t *hi_zmm = (void *)((uintptr_t)*bufp +
+	    info->pri_offset);
+	hi_zmm->prx_rsvd[1]._l[9] = 0x34567;
+}
+#endif	/* __i386 */
+
+static const bad_xregs_test_t bad_tests[] = {
+	{ "no data (NULL buffer)", EINVAL, XSU_YMM, bad_xregs_no_data },
+	{ "NULL buffer, non-zero count", EFAULT, XSU_YMM, bad_xregs_null_buf },
+	{ "incomplete prxregset_hdr_t", EINVAL, XSU_YMM, bad_xregs_short_hdr },
+	{ "prxregset_hdr_t has wrong type", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_bad_type },
+	{ "prxregset_hdr_t size is too large", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_too_large },
+	{ "prxregset_hdr_t size bigger than /proc write", EINVAL, XSU_YMM,
+	    bad_xregs_missing_data },
+	{ "prxregset_hdr_t invalid flags", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_bad_flags },
+	{ "prxregset_hdr_t invalid pad[0]", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_bad_pad0 },
+	{ "prxregset_hdr_t invalid pad[1]", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_bad_pad1 },
+	{ "prxregset_hdr_t invalid pad[2]", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_bad_pad2 },
+	{ "prxregset_hdr_t invalid pad[3]", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_bad_pad3 },
+	{ "prxregset_hdr_t no info structures", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_no_info },
+	{ "prxregset_hdr_t len doesn't cover info structures", EINVAL, XSU_YMM,
+	    bad_xregs_hdr_no_info_len },
+	{ "prxregset_info_t has bad flags", EINVAL, XSU_YMM,
+	    bad_xregs_info_flags },
+	{ "prxregset_info_t has bad type", EINVAL, XSU_YMM,
+	    bad_xregs_info_type },
+	{ "prxregset_info_t has bad len (XCR)", EINVAL, XSU_YMM,
+	    bad_xregs_info_xcr_len },
+	{ "prxregset_info_t has bad align (XCR)", EINVAL, XSU_YMM,
+	    bad_xregs_info_xcr_off },
+	{ "prxregset_info_t has bad len (XSAVE)", EINVAL, XSU_YMM,
+	    bad_xregs_info_xsave_len },
+	{ "prxregset_info_t has bad align (XSAVE)", EINVAL, XSU_YMM,
+	    bad_xregs_info_xsave_off },
+	{ "prxregset_info_t has bad len (YMM)", EINVAL, XSU_YMM,
+	    bad_xregs_info_ymm_len },
+	{ "prxregset_info_t has bad align (YMM)", EINVAL, XSU_YMM,
+	    bad_xregs_info_ymm_off },
+	{ "prxregset_info_t has bad len (OPMASK)", EINVAL, XSU_ZMM,
+	    bad_xregs_info_opmask_len },
+	{ "prxregset_info_t has bad align (OPMASK)", EINVAL, XSU_ZMM,
+	    bad_xregs_info_opmask_off },
+	{ "prxregset_info_t has bad len (ZMM)", EINVAL, XSU_ZMM,
+	    bad_xregs_info_zmm_len },
+	{ "prxregset_info_t has bad align (ZMM)", EINVAL, XSU_ZMM,
+	    bad_xregs_info_zmm_off },
+	{ "prxregset_info_t has bad len (HI ZMM)", EINVAL, XSU_ZMM,
+	    bad_xregs_info_hi_zmm_len },
+	{ "prxregset_info_t has bad align (HI ZMM)", EINVAL, XSU_ZMM,
+	    bad_xregs_info_hi_zmm_off },
+	{ "prxregset_info_t offset exceeds total len (offset beyond len)",
+	    EINVAL, XSU_YMM, bad_xregs_info_exceeds_len0 },
+	{ "prxregset_info_t offset exceeds total len (size+offset beyond len)",
+	    EINVAL, XSU_YMM, bad_xregs_info_exceeds_len1 },
+	{ "prxregset_info_t offset overlaps info", EINVAL, XSU_YMM,
+	    bad_xregs_info_overlaps },
+	{ "prxregset_t missing xsave struct", EINVAL, XSU_YMM,
+	    bad_xregs_no_xsave },
+	{ "prxregset_t missing xstate bit-vector entry", EINVAL, XSU_YMM,
+	    bad_xregs_missing_xstate },
+	{ "prxregset_xcr_t modified xcr0", EINVAL, XSU_YMM,
+	    bad_xregs_xcr_bad_xcr0 },
+	{ "prxregset_xcr_t modified xfd", EINVAL, XSU_YMM,
+	    bad_xregs_xcr_bad_xfd },
+	{ "prxregset_xcr_t modified pad[0]", EINVAL, XSU_YMM,
+	    bad_xregs_xcr_bad_pad0 },
+	{ "prxregset_xcr_t modified pad[1]", EINVAL, XSU_YMM,
+	    bad_xregs_xcr_bad_pad1 },
+	{ "prxregset_xsave_t illegal xbv comp", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_xbv },
+	{ "prxregset_xsave_t illegal compressed comp", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_xcomp },
+	{ "prxregset_xsave_t illegal rsvd[0]", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_rsvd0 },
+	{ "prxregset_xsave_t illegal rsvd[1]", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_rsvd1 },
+	{ "prxregset_xsave_t illegal rsvd[2]", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_rsvd2 },
+	{ "prxregset_xsave_t illegal rsvd[3]", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_rsvd3 },
+	{ "prxregset_xsave_t illegal rsvd[4]", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_rsvd4 },
+	{ "prxregset_xsave_t illegal rsvd[5]", EINVAL, XSU_YMM,
+	    bad_xregs_xsave_bad_rsvd5 },
+/*
+ * These next sets of tests are specific to 32-bit binaries as they're not
+ * allowed to access a bunch of the additional registers that exist.
+ */
+#ifdef __i386
+	{ "prxregset_ymm_t has non-zero reserved i386 reg", EINVAL, XSU_YMM,
+	    bad_xregs_ymm_ilp32 },
+	{ "prxregset_zmm_t has non-zero reserved i386 reg", EINVAL, XSU_ZMM,
+	    bad_xregs_zmm_ilp32 },
+	{ "prxregset_hi_zmm_t has non-zero reserved i386 reg", EINVAL, XSU_ZMM,
+	    bad_xregs_hi_zmm_ilp32 },
+#endif
+};
+
+int
+main(void)
+{
+	int ret;
+	int estatus = EXIT_SUCCESS;
+	struct ps_prochandle *P;
+	struct ps_lwphandle *L;
+	thread_t targ;
+	uint32_t hwsup;
+	uint32_t nskip = 0;
+
+	hwsup = xsu_hwsupport();
+	P = Pgrab(getpid(), PGRAB_RDONLY, &ret);
+	if (P == NULL) {
+		errx(EXIT_FAILURE, "failed to grab ourself: %s",
+		    Pgrab_error(ret));
+	}
+
+	ret = thr_create(NULL, 0, xsu_sleeper_thread, NULL, THR_DETACHED,
+	    &targ);
+	if (ret != 0) {
+		errc(EXIT_FAILURE, ret, "failed to create sleeper thread");
+	}
+
+	L = Lgrab(P, targ, &ret);
+	if (L == NULL) {
+		errx(EXIT_FAILURE, "failed to grab our sleeper thread: %s",
+		    Lgrab_error(ret));
+	}
+
+	ret = Lstop(L, 0);
+	if (ret != 0) {
+		err(EXIT_FAILURE, "failed to stop the sleeper thread");
+	}
+
+	if (Lgetxregs(L, &bad_xregs_pxr, &bad_xregs_size) != 0) {
+		err(EXIT_FAILURE, "failed to get basic xregs");
+	}
+
+	if (bad_xregs_size < sizeof (prxregset_hdr_t)) {
+		errx(EXIT_FAILURE, "found bad regset size: %zu",
+		    bad_xregs_size);
+	}
+
+	for (size_t i = 0; i < ARRAY_SIZE(bad_tests); i++) {
+		void *buf = NULL;
+		size_t len = 0;
+
+		if (bad_tests[i].bxt_min > hwsup) {
+			warnx("TEST SKIPPED: %s: requires greater hwsup than "
+			    "supported (0x%x)", bad_tests[i].bxt_desc,
+			    bad_tests[i].bxt_min);
+			nskip++;
+			continue;
+		}
+
+		bad_tests[i].bxt_setup(&buf, &len);
+		if (Lsetxregs(L, buf, len) != -1) {
+			warnx("TEST FAILED: %s: Lsetxregs returned 0, not -1!",
+			    bad_tests[i].bxt_desc);
+			estatus = EXIT_FAILURE;
+		} else if (errno != bad_tests[i].bxt_errno) {
+			warnx("TEST FAILED: %s: Lsetxregs errno was %d, "
+			    "expected %d", bad_tests[i].bxt_desc, errno,
+			    bad_tests[i].bxt_errno);
+			estatus = EXIT_FAILURE;
+		} else {
+			(void) printf("TEST PASSED: %s\n",
+			    bad_tests[i].bxt_desc);
+		}
+		free(buf);
+	}
+
+	if (estatus == EXIT_SUCCESS && nskip > 0) {
+		warnx("While tests were successful, %u tests were skipped "
+		    "due to missing hardware support", nskip);
+	}
+
+	exit(estatus);
+}
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/mdb_xregs.ymm.32	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,52 @@
+x86 FPU with 256-bit %ymm registers
+xcr0            0x7
+xfd             0x0
+xstate_bv       0x7
+xcomp_bv        0x0
+
+%ymm0  [255:128] 0x12900929 12900928 12900927 12900926
+%xmm0  [127:0]   0x12900925 12900924 12900923 12900922
+
+%ymm1  [255:128] 0x12900931 12900930 1290092f 1290092e
+%xmm1  [127:0]   0x1290092d 1290092c 1290092b 1290092a
+
+%ymm2  [255:128] 0x12900939 12900938 12900937 12900936
+%xmm2  [127:0]   0x12900935 12900934 12900933 12900932
+
+%ymm3  [255:128] 0x12900941 12900940 1290093f 1290093e
+%xmm3  [127:0]   0x1290093d 1290093c 1290093b 1290093a
+
+%ymm4  [255:128] 0x12900949 12900948 12900947 12900946
+%xmm4  [127:0]   0x12900945 12900944 12900943 12900942
+
+%ymm5  [255:128] 0x12900951 12900950 1290094f 1290094e
+%xmm5  [127:0]   0x1290094d 1290094c 1290094b 1290094a
+
+%ymm6  [255:128] 0x12900959 12900958 12900957 12900956
+%xmm6  [127:0]   0x12900955 12900954 12900953 12900952
+
+%ymm7  [255:128] 0x12900961 12900960 1290095f 1290095e
+%xmm7  [127:0]   0x1290095d 1290095c 1290095b 1290095a
+
+387 and FP Control State
+cw     0x137f (IM|DM|ZM|OM|UM|PM|SIG64|RTN|A)
+sw     0x0000 (TOP=0t0) (0)
+xcp sw 0x0000 (0)
+
+ipoff  0
+cssel  0x43
+dtoff  0
+dtsel  0x4b
+
+%st0   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st1   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st2   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st3   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st4   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st5   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st6   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st7   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+
+SSE Control State
+mxcsr  0x1f80 (IM|DM|ZM|OM|UM|PM|RTN)
+xcp    0x0000 (RTN)
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/mdb_xregs.ymm.64	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,75 @@
+x86 FPU with 256-bit %ymm registers
+xcr0            0x7
+xfd             0x0
+xstate_bv       0x7
+xcomp_bv        0x0
+
+%ymm0  [255:128] 0x1190045d 1190045c 1190045b 1190045a
+%xmm0  [127:0]   0x11900459 11900458 11900457 11900456
+
+%ymm1  [255:128] 0x11900465 11900464 11900463 11900462
+%xmm1  [127:0]   0x11900461 11900460 1190045f 1190045e
+
+%ymm2  [255:128] 0x1190046d 1190046c 1190046b 1190046a
+%xmm2  [127:0]   0x11900469 11900468 11900467 11900466
+
+%ymm3  [255:128] 0x11900475 11900474 11900473 11900472
+%xmm3  [127:0]   0x11900471 11900470 1190046f 1190046e
+
+%ymm4  [255:128] 0x1190047d 1190047c 1190047b 1190047a
+%xmm4  [127:0]   0x11900479 11900478 11900477 11900476
+
+%ymm5  [255:128] 0x11900485 11900484 11900483 11900482
+%xmm5  [127:0]   0x11900481 11900480 1190047f 1190047e
+
+%ymm6  [255:128] 0x1190048d 1190048c 1190048b 1190048a
+%xmm6  [127:0]   0x11900489 11900488 11900487 11900486
+
+%ymm7  [255:128] 0x11900495 11900494 11900493 11900492
+%xmm7  [127:0]   0x11900491 11900490 1190048f 1190048e
+
+%ymm8  [255:128] 0x1190049d 1190049c 1190049b 1190049a
+%xmm8  [127:0]   0x11900499 11900498 11900497 11900496
+
+%ymm9  [255:128] 0x119004a5 119004a4 119004a3 119004a2
+%xmm9  [127:0]   0x119004a1 119004a0 1190049f 1190049e
+
+%ymm10 [255:128] 0x119004ad 119004ac 119004ab 119004aa
+%xmm10 [127:0]   0x119004a9 119004a8 119004a7 119004a6
+
+%ymm11 [255:128] 0x119004b5 119004b4 119004b3 119004b2
+%xmm11 [127:0]   0x119004b1 119004b0 119004af 119004ae
+
+%ymm12 [255:128] 0x119004bd 119004bc 119004bb 119004ba
+%xmm12 [127:0]   0x119004b9 119004b8 119004b7 119004b6
+
+%ymm13 [255:128] 0x119004c5 119004c4 119004c3 119004c2
+%xmm13 [127:0]   0x119004c1 119004c0 119004bf 119004be
+
+%ymm14 [255:128] 0x119004cd 119004cc 119004cb 119004ca
+%xmm14 [127:0]   0x119004c9 119004c8 119004c7 119004c6
+
+%ymm15 [255:128] 0x119004d5 119004d4 119004d3 119004d2
+%xmm15 [127:0]   0x119004d1 119004d0 119004cf 119004ce
+
+387 and FP Control State
+cw     0x137f (IM|DM|ZM|OM|UM|PM|SIG64|RTN|A)
+sw     0x0000 (TOP=0t0) (0)
+xcp sw 0x0000 (0)
+
+fop    0x0
+rip    0x0
+rdp    0x0
+
+%st0   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st1   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st2   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st3   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st4   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st5   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st6   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st7   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+
+SSE Control State
+mxcsr  0x1f80 (IM|DM|ZM|OM|UM|PM|RTN)
+xcp    0x0000 (RTN)
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/mdb_xregs.zmm.32	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,73 @@
+x86 FPU with 512-bit %zmm registers
+xcr0            0xe7
+xfd             0x0
+xstate_bv       0x67
+xcomp_bv        0x0
+
+%zmm0  [511:384] 0x12900931 12900930 1290092f 1290092e
+       [383:256] 0x1290092d 1290092c 1290092b 1290092a
+%ymm0  [255:128] 0x12900929 12900928 12900927 12900926
+%xmm0  [127:0]   0x12900925 12900924 12900923 12900922
+
+%zmm1  [511:384] 0x12900941 12900940 1290093f 1290093e
+       [383:256] 0x1290093d 1290093c 1290093b 1290093a
+%ymm1  [255:128] 0x12900939 12900938 12900937 12900936
+%xmm1  [127:0]   0x12900935 12900934 12900933 12900932
+
+%zmm2  [511:384] 0x12900951 12900950 1290094f 1290094e
+       [383:256] 0x1290094d 1290094c 1290094b 1290094a
+%ymm2  [255:128] 0x12900949 12900948 12900947 12900946
+%xmm2  [127:0]   0x12900945 12900944 12900943 12900942
+
+%zmm3  [511:384] 0x12900961 12900960 1290095f 1290095e
+       [383:256] 0x1290095d 1290095c 1290095b 1290095a
+%ymm3  [255:128] 0x12900959 12900958 12900957 12900956
+%xmm3  [127:0]   0x12900955 12900954 12900953 12900952
+
+%zmm4  [511:384] 0x12900971 12900970 1290096f 1290096e
+       [383:256] 0x1290096d 1290096c 1290096b 1290096a
+%ymm4  [255:128] 0x12900969 12900968 12900967 12900966
+%xmm4  [127:0]   0x12900965 12900964 12900963 12900962
+
+%zmm5  [511:384] 0x12900981 12900980 1290097f 1290097e
+       [383:256] 0x1290097d 1290097c 1290097b 1290097a
+%ymm5  [255:128] 0x12900979 12900978 12900977 12900976
+%xmm5  [127:0]   0x12900975 12900974 12900973 12900972
+
+%zmm6  [511:384] 0x12900991 12900990 1290098f 1290098e
+       [383:256] 0x1290098d 1290098c 1290098b 1290098a
+%ymm6  [255:128] 0x12900989 12900988 12900987 12900986
+%xmm6  [127:0]   0x12900985 12900984 12900983 12900982
+
+%zmm7  [511:384] 0x129009a1 129009a0 1290099f 1290099e
+       [383:256] 0x1290099d 1290099c 1290099b 1290099a
+%ymm7  [255:128] 0x12900999 12900998 12900997 12900996
+%xmm7  [127:0]   0x12900995 12900994 12900993 12900992
+
+%k0  0x00000000129009a2         %k1  0x00000000129009a3
+%k2  0x00000000129009a6         %k3  0x00000000129009a7
+%k4  0x00000000129009aa         %k5  0x00000000129009ab
+%k6  0x00000000129009ae         %k7  0x00000000129009af
+
+387 and FP Control State
+cw     0x137f (IM|DM|ZM|OM|UM|PM|SIG64|RTN|A)
+sw     0x0000 (TOP=0t0) (0)
+xcp sw 0x0000 (0)
+
+ipoff  0
+cssel  0x43
+dtoff  0
+dtsel  0x4b
+
+%st0   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st1   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st2   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st3   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st4   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st5   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st6   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st7   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+
+SSE Control State
+mxcsr  0x1f80 (IM|DM|ZM|OM|UM|PM|RTN)
+xcp    0x0000 (RTN)
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/mdb_xregs.zmm.64	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,192 @@
+x86 FPU with 512-bit %zmm registers
+xcr0            0xe7
+xfd             0x0
+xstate_bv       0xe7
+xcomp_bv        0x0
+
+%zmm0  [511:384] 0x11900465 11900464 11900463 11900462
+       [383:256] 0x11900461 11900460 1190045f 1190045e
+%ymm0  [255:128] 0x1190045d 1190045c 1190045b 1190045a
+%xmm0  [127:0]   0x11900459 11900458 11900457 11900456
+
+%zmm1  [511:384] 0x11900475 11900474 11900473 11900472
+       [383:256] 0x11900471 11900470 1190046f 1190046e
+%ymm1  [255:128] 0x1190046d 1190046c 1190046b 1190046a
+%xmm1  [127:0]   0x11900469 11900468 11900467 11900466
+
+%zmm2  [511:384] 0x11900485 11900484 11900483 11900482
+       [383:256] 0x11900481 11900480 1190047f 1190047e
+%ymm2  [255:128] 0x1190047d 1190047c 1190047b 1190047a
+%xmm2  [127:0]   0x11900479 11900478 11900477 11900476
+
+%zmm3  [511:384] 0x11900495 11900494 11900493 11900492
+       [383:256] 0x11900491 11900490 1190048f 1190048e
+%ymm3  [255:128] 0x1190048d 1190048c 1190048b 1190048a
+%xmm3  [127:0]   0x11900489 11900488 11900487 11900486
+
+%zmm4  [511:384] 0x119004a5 119004a4 119004a3 119004a2
+       [383:256] 0x119004a1 119004a0 1190049f 1190049e
+%ymm4  [255:128] 0x1190049d 1190049c 1190049b 1190049a
+%xmm4  [127:0]   0x11900499 11900498 11900497 11900496
+
+%zmm5  [511:384] 0x119004b5 119004b4 119004b3 119004b2
+       [383:256] 0x119004b1 119004b0 119004af 119004ae
+%ymm5  [255:128] 0x119004ad 119004ac 119004ab 119004aa
+%xmm5  [127:0]   0x119004a9 119004a8 119004a7 119004a6
+
+%zmm6  [511:384] 0x119004c5 119004c4 119004c3 119004c2
+       [383:256] 0x119004c1 119004c0 119004bf 119004be
+%ymm6  [255:128] 0x119004bd 119004bc 119004bb 119004ba
+%xmm6  [127:0]   0x119004b9 119004b8 119004b7 119004b6
+
+%zmm7  [511:384] 0x119004d5 119004d4 119004d3 119004d2
+       [383:256] 0x119004d1 119004d0 119004cf 119004ce
+%ymm7  [255:128] 0x119004cd 119004cc 119004cb 119004ca
+%xmm7  [127:0]   0x119004c9 119004c8 119004c7 119004c6
+
+%zmm8  [511:384] 0x119004e5 119004e4 119004e3 119004e2
+       [383:256] 0x119004e1 119004e0 119004df 119004de
+%ymm8  [255:128] 0x119004dd 119004dc 119004db 119004da
+%xmm8  [127:0]   0x119004d9 119004d8 119004d7 119004d6
+
+%zmm9  [511:384] 0x119004f5 119004f4 119004f3 119004f2
+       [383:256] 0x119004f1 119004f0 119004ef 119004ee
+%ymm9  [255:128] 0x119004ed 119004ec 119004eb 119004ea
+%xmm9  [127:0]   0x119004e9 119004e8 119004e7 119004e6
+
+%zmm10 [511:384] 0x11900505 11900504 11900503 11900502
+       [383:256] 0x11900501 11900500 119004ff 119004fe
+%ymm10 [255:128] 0x119004fd 119004fc 119004fb 119004fa
+%xmm10 [127:0]   0x119004f9 119004f8 119004f7 119004f6
+
+%zmm11 [511:384] 0x11900515 11900514 11900513 11900512
+       [383:256] 0x11900511 11900510 1190050f 1190050e
+%ymm11 [255:128] 0x1190050d 1190050c 1190050b 1190050a
+%xmm11 [127:0]   0x11900509 11900508 11900507 11900506
+
+%zmm12 [511:384] 0x11900525 11900524 11900523 11900522
+       [383:256] 0x11900521 11900520 1190051f 1190051e
+%ymm12 [255:128] 0x1190051d 1190051c 1190051b 1190051a
+%xmm12 [127:0]   0x11900519 11900518 11900517 11900516
+
+%zmm13 [511:384] 0x11900535 11900534 11900533 11900532
+       [383:256] 0x11900531 11900530 1190052f 1190052e
+%ymm13 [255:128] 0x1190052d 1190052c 1190052b 1190052a
+%xmm13 [127:0]   0x11900529 11900528 11900527 11900526
+
+%zmm14 [511:384] 0x11900545 11900544 11900543 11900542
+       [383:256] 0x11900541 11900540 1190053f 1190053e
+%ymm14 [255:128] 0x1190053d 1190053c 1190053b 1190053a
+%xmm14 [127:0]   0x11900539 11900538 11900537 11900536
+
+%zmm15 [511:384] 0x11900555 11900554 11900553 11900552
+       [383:256] 0x11900551 11900550 1190054f 1190054e
+%ymm15 [255:128] 0x1190054d 1190054c 1190054b 1190054a
+%xmm15 [127:0]   0x11900549 11900548 11900547 11900546
+
+%zmm16 [511:384] 0x11900565 11900564 11900563 11900562
+       [383:256] 0x11900561 11900560 1190055f 1190055e
+%zmm16 [255:128] 0x1190055d 1190055c 1190055b 1190055a
+%zmm16 [127:0]   0x11900559 11900558 11900557 11900556
+
+%zmm17 [511:384] 0x11900575 11900574 11900573 11900572
+       [383:256] 0x11900571 11900570 1190056f 1190056e
+%zmm17 [255:128] 0x1190056d 1190056c 1190056b 1190056a
+%zmm17 [127:0]   0x11900569 11900568 11900567 11900566
+
+%zmm18 [511:384] 0x11900585 11900584 11900583 11900582
+       [383:256] 0x11900581 11900580 1190057f 1190057e
+%zmm18 [255:128] 0x1190057d 1190057c 1190057b 1190057a
+%zmm18 [127:0]   0x11900579 11900578 11900577 11900576
+
+%zmm19 [511:384] 0x11900595 11900594 11900593 11900592
+       [383:256] 0x11900591 11900590 1190058f 1190058e
+%zmm19 [255:128] 0x1190058d 1190058c 1190058b 1190058a
+%zmm19 [127:0]   0x11900589 11900588 11900587 11900586
+
+%zmm20 [511:384] 0x119005a5 119005a4 119005a3 119005a2
+       [383:256] 0x119005a1 119005a0 1190059f 1190059e
+%zmm20 [255:128] 0x1190059d 1190059c 1190059b 1190059a
+%zmm20 [127:0]   0x11900599 11900598 11900597 11900596
+
+%zmm21 [511:384] 0x119005b5 119005b4 119005b3 119005b2
+       [383:256] 0x119005b1 119005b0 119005af 119005ae
+%zmm21 [255:128] 0x119005ad 119005ac 119005ab 119005aa
+%zmm21 [127:0]   0x119005a9 119005a8 119005a7 119005a6
+
+%zmm22 [511:384] 0x119005c5 119005c4 119005c3 119005c2
+       [383:256] 0x119005c1 119005c0 119005bf 119005be
+%zmm22 [255:128] 0x119005bd 119005bc 119005bb 119005ba
+%zmm22 [127:0]   0x119005b9 119005b8 119005b7 119005b6
+
+%zmm23 [511:384] 0x119005d5 119005d4 119005d3 119005d2
+       [383:256] 0x119005d1 119005d0 119005cf 119005ce
+%zmm23 [255:128] 0x119005cd 119005cc 119005cb 119005ca
+%zmm23 [127:0]   0x119005c9 119005c8 119005c7 119005c6
+
+%zmm24 [511:384] 0x119005e5 119005e4 119005e3 119005e2
+       [383:256] 0x119005e1 119005e0 119005df 119005de
+%zmm24 [255:128] 0x119005dd 119005dc 119005db 119005da
+%zmm24 [127:0]   0x119005d9 119005d8 119005d7 119005d6
+
+%zmm25 [511:384] 0x119005f5 119005f4 119005f3 119005f2
+       [383:256] 0x119005f1 119005f0 119005ef 119005ee
+%zmm25 [255:128] 0x119005ed 119005ec 119005eb 119005ea
+%zmm25 [127:0]   0x119005e9 119005e8 119005e7 119005e6
+
+%zmm26 [511:384] 0x11900605 11900604 11900603 11900602
+       [383:256] 0x11900601 11900600 119005ff 119005fe
+%zmm26 [255:128] 0x119005fd 119005fc 119005fb 119005fa
+%zmm26 [127:0]   0x119005f9 119005f8 119005f7 119005f6
+
+%zmm27 [511:384] 0x11900615 11900614 11900613 11900612
+       [383:256] 0x11900611 11900610 1190060f 1190060e
+%zmm27 [255:128] 0x1190060d 1190060c 1190060b 1190060a
+%zmm27 [127:0]   0x11900609 11900608 11900607 11900606
+
+%zmm28 [511:384] 0x11900625 11900624 11900623 11900622
+       [383:256] 0x11900621 11900620 1190061f 1190061e
+%zmm28 [255:128] 0x1190061d 1190061c 1190061b 1190061a
+%zmm28 [127:0]   0x11900619 11900618 11900617 11900616
+
+%zmm29 [511:384] 0x11900635 11900634 11900633 11900632
+       [383:256] 0x11900631 11900630 1190062f 1190062e
+%zmm29 [255:128] 0x1190062d 1190062c 1190062b 1190062a
+%zmm29 [127:0]   0x11900629 11900628 11900627 11900626
+
+%zmm30 [511:384] 0x11900645 11900644 11900643 11900642
+       [383:256] 0x11900641 11900640 1190063f 1190063e
+%zmm30 [255:128] 0x1190063d 1190063c 1190063b 1190063a
+%zmm30 [127:0]   0x11900639 11900638 11900637 11900636
+
+%zmm31 [511:384] 0x11900655 11900654 11900653 11900652
+       [383:256] 0x11900651 11900650 1190064f 1190064e
+%zmm31 [255:128] 0x1190064d 1190064c 1190064b 1190064a
+%zmm31 [127:0]   0x11900649 11900648 11900647 11900646
+
+%k0  0x0000000011900656         %k1  0x0000000011900658
+%k2  0x000000001190065a         %k3  0x000000001190065c
+%k4  0x000000001190065e         %k5  0x0000000011900660
+%k6  0x0000000011900662         %k7  0x0000000011900664
+
+387 and FP Control State
+cw     0x137f (IM|DM|ZM|OM|UM|PM|SIG64|RTN|A)
+sw     0x0000 (TOP=0t0) (0)
+xcp sw 0x0000 (0)
+
+fop    0x0
+rip    0x0
+rdp    0x0
+
+%st0   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st1   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st2   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st3   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st4   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st5   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st6   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+%st7   0x0000.0000000000000000 = +0.0000000000000000e+00 empty
+
+SSE Control State
+mxcsr  0x1f80 (IM|DM|ZM|OM|UM|PM|RTN)
+xcp    0x0000 (RTN)
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.fpregs.ymm.32	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,16 @@
+ymm[00] [03:00] = { 0xa019090b 0xa019090a 0xa0190909 0xa0190908 }
+ymm[00] [07:04] = { 0x20190910 0x2019090f 0x2019090e 0x2019090d }
+ymm[01] [03:00] = { 0xa019090f 0xa019090e 0xa019090d 0xa019090c }
+ymm[01] [07:04] = { 0x20190918 0x20190917 0x20190916 0x20190915 }
+ymm[02] [03:00] = { 0xa0190913 0xa0190912 0xa0190911 0xa0190910 }
+ymm[02] [07:04] = { 0x20190920 0x2019091f 0x2019091e 0x2019091d }
+ymm[03] [03:00] = { 0xa0190917 0xa0190916 0xa0190915 0xa0190914 }
+ymm[03] [07:04] = { 0x20190928 0x20190927 0x20190926 0x20190925 }
+ymm[04] [03:00] = { 0xa019091b 0xa019091a 0xa0190919 0xa0190918 }
+ymm[04] [07:04] = { 0x20190930 0x2019092f 0x2019092e 0x2019092d }
+ymm[05] [03:00] = { 0xa019091f 0xa019091e 0xa019091d 0xa019091c }
+ymm[05] [07:04] = { 0x20190938 0x20190937 0x20190936 0x20190935 }
+ymm[06] [03:00] = { 0xa0190923 0xa0190922 0xa0190921 0xa0190920 }
+ymm[06] [07:04] = { 0x20190940 0x2019093f 0x2019093e 0x2019093d }
+ymm[07] [03:00] = { 0xa0190927 0xa0190926 0xa0190925 0xa0190924 }
+ymm[07] [07:04] = { 0x20190948 0x20190947 0x20190946 0x20190945 }
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.fpregs.ymm.64	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,32 @@
+ymm[00] [03:00] = { 0xa8186004 0xa8186003 0xa8186002 0xa8186001 }
+ymm[00] [07:04] = { 0x28186009 0x28186008 0x28186007 0x28186006 }
+ymm[01] [03:00] = { 0xa8186008 0xa8186007 0xa8186006 0xa8186005 }
+ymm[01] [07:04] = { 0x28186011 0x28186010 0x2818600f 0x2818600e }
+ymm[02] [03:00] = { 0xa818600c 0xa818600b 0xa818600a 0xa8186009 }
+ymm[02] [07:04] = { 0x28186019 0x28186018 0x28186017 0x28186016 }
+ymm[03] [03:00] = { 0xa8186010 0xa818600f 0xa818600e 0xa818600d }
+ymm[03] [07:04] = { 0x28186021 0x28186020 0x2818601f 0x2818601e }
+ymm[04] [03:00] = { 0xa8186014 0xa8186013 0xa8186012 0xa8186011 }
+ymm[04] [07:04] = { 0x28186029 0x28186028 0x28186027 0x28186026 }
+ymm[05] [03:00] = { 0xa8186018 0xa8186017 0xa8186016 0xa8186015 }
+ymm[05] [07:04] = { 0x28186031 0x28186030 0x2818602f 0x2818602e }
+ymm[06] [03:00] = { 0xa818601c 0xa818601b 0xa818601a 0xa8186019 }
+ymm[06] [07:04] = { 0x28186039 0x28186038 0x28186037 0x28186036 }
+ymm[07] [03:00] = { 0xa8186020 0xa818601f 0xa818601e 0xa818601d }
+ymm[07] [07:04] = { 0x28186041 0x28186040 0x2818603f 0x2818603e }
+ymm[08] [03:00] = { 0xa8186024 0xa8186023 0xa8186022 0xa8186021 }
+ymm[08] [07:04] = { 0x28186049 0x28186048 0x28186047 0x28186046 }
+ymm[09] [03:00] = { 0xa8186028 0xa8186027 0xa8186026 0xa8186025 }
+ymm[09] [07:04] = { 0x28186051 0x28186050 0x2818604f 0x2818604e }
+ymm[10] [03:00] = { 0xa818602c 0xa818602b 0xa818602a 0xa8186029 }
+ymm[10] [07:04] = { 0x28186059 0x28186058 0x28186057 0x28186056 }
+ymm[11] [03:00] = { 0xa8186030 0xa818602f 0xa818602e 0xa818602d }
+ymm[11] [07:04] = { 0x28186061 0x28186060 0x2818605f 0x2818605e }
+ymm[12] [03:00] = { 0xa8186034 0xa8186033 0xa8186032 0xa8186031 }
+ymm[12] [07:04] = { 0x28186069 0x28186068 0x28186067 0x28186066 }
+ymm[13] [03:00] = { 0xa8186038 0xa8186037 0xa8186036 0xa8186035 }
+ymm[13] [07:04] = { 0x28186071 0x28186070 0x2818606f 0x2818606e }
+ymm[14] [03:00] = { 0xa818603c 0xa818603b 0xa818603a 0xa8186039 }
+ymm[14] [07:04] = { 0x28186079 0x28186078 0x28186077 0x28186076 }
+ymm[15] [03:00] = { 0xa8186040 0xa818603f 0xa818603e 0xa818603d }
+ymm[15] [07:04] = { 0x28186081 0x28186080 0x2818607f 0x2818607e }
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.fpregs.zmm.32	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,40 @@
+zmm[00] [03:00] = { 0xa019090b 0xa019090a 0xa0190909 0xa0190908 }
+zmm[00] [07:04] = { 0x20190910 0x2019090f 0x2019090e 0x2019090d }
+zmm[00] [11:08] = { 0x20190914 0x20190913 0x20190912 0x20190911 }
+zmm[00] [15:12] = { 0x20190918 0x20190917 0x20190916 0x20190915 }
+zmm[01] [03:00] = { 0xa019090f 0xa019090e 0xa019090d 0xa019090c }
+zmm[01] [07:04] = { 0x20190920 0x2019091f 0x2019091e 0x2019091d }
+zmm[01] [11:08] = { 0x20190924 0x20190923 0x20190922 0x20190921 }
+zmm[01] [15:12] = { 0x20190928 0x20190927 0x20190926 0x20190925 }
+zmm[02] [03:00] = { 0xa0190913 0xa0190912 0xa0190911 0xa0190910 }
+zmm[02] [07:04] = { 0x20190930 0x2019092f 0x2019092e 0x2019092d }
+zmm[02] [11:08] = { 0x20190934 0x20190933 0x20190932 0x20190931 }
+zmm[02] [15:12] = { 0x20190938 0x20190937 0x20190936 0x20190935 }
+zmm[03] [03:00] = { 0xa0190917 0xa0190916 0xa0190915 0xa0190914 }
+zmm[03] [07:04] = { 0x20190940 0x2019093f 0x2019093e 0x2019093d }
+zmm[03] [11:08] = { 0x20190944 0x20190943 0x20190942 0x20190941 }
+zmm[03] [15:12] = { 0x20190948 0x20190947 0x20190946 0x20190945 }
+zmm[04] [03:00] = { 0xa019091b 0xa019091a 0xa0190919 0xa0190918 }
+zmm[04] [07:04] = { 0x20190950 0x2019094f 0x2019094e 0x2019094d }
+zmm[04] [11:08] = { 0x20190954 0x20190953 0x20190952 0x20190951 }
+zmm[04] [15:12] = { 0x20190958 0x20190957 0x20190956 0x20190955 }
+zmm[05] [03:00] = { 0xa019091f 0xa019091e 0xa019091d 0xa019091c }
+zmm[05] [07:04] = { 0x20190960 0x2019095f 0x2019095e 0x2019095d }
+zmm[05] [11:08] = { 0x20190964 0x20190963 0x20190962 0x20190961 }
+zmm[05] [15:12] = { 0x20190968 0x20190967 0x20190966 0x20190965 }
+zmm[06] [03:00] = { 0xa0190923 0xa0190922 0xa0190921 0xa0190920 }
+zmm[06] [07:04] = { 0x20190970 0x2019096f 0x2019096e 0x2019096d }
+zmm[06] [11:08] = { 0x20190974 0x20190973 0x20190972 0x20190971 }
+zmm[06] [15:12] = { 0x20190978 0x20190977 0x20190976 0x20190975 }
+zmm[07] [03:00] = { 0xa0190927 0xa0190926 0xa0190925 0xa0190924 }
+zmm[07] [07:04] = { 0x20190980 0x2019097f 0x2019097e 0x2019097d }
+zmm[07] [11:08] = { 0x20190984 0x20190983 0x20190982 0x20190981 }
+zmm[07] [15:12] = { 0x20190988 0x20190987 0x20190986 0x20190985 }
+%k0 0x2019098a20190989
+%k1 0x2019098c2019098b
+%k2 0x2019098e2019098d
+%k3 0x201909902019098f
+%k4 0x2019099220190991
+%k5 0x2019099420190993
+%k6 0x2019099620190995
+%k7 0x2019099820190997
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.fpregs.zmm.64	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,136 @@
+zmm[00] [03:00] = { 0xa8186004 0xa8186003 0xa8186002 0xa8186001 }
+zmm[00] [07:04] = { 0x28186009 0x28186008 0x28186007 0x28186006 }
+zmm[00] [11:08] = { 0x2818600d 0x2818600c 0x2818600b 0x2818600a }
+zmm[00] [15:12] = { 0x28186011 0x28186010 0x2818600f 0x2818600e }
+zmm[01] [03:00] = { 0xa8186008 0xa8186007 0xa8186006 0xa8186005 }
+zmm[01] [07:04] = { 0x28186019 0x28186018 0x28186017 0x28186016 }
+zmm[01] [11:08] = { 0x2818601d 0x2818601c 0x2818601b 0x2818601a }
+zmm[01] [15:12] = { 0x28186021 0x28186020 0x2818601f 0x2818601e }
+zmm[02] [03:00] = { 0xa818600c 0xa818600b 0xa818600a 0xa8186009 }
+zmm[02] [07:04] = { 0x28186029 0x28186028 0x28186027 0x28186026 }
+zmm[02] [11:08] = { 0x2818602d 0x2818602c 0x2818602b 0x2818602a }
+zmm[02] [15:12] = { 0x28186031 0x28186030 0x2818602f 0x2818602e }
+zmm[03] [03:00] = { 0xa8186010 0xa818600f 0xa818600e 0xa818600d }
+zmm[03] [07:04] = { 0x28186039 0x28186038 0x28186037 0x28186036 }
+zmm[03] [11:08] = { 0x2818603d 0x2818603c 0x2818603b 0x2818603a }
+zmm[03] [15:12] = { 0x28186041 0x28186040 0x2818603f 0x2818603e }
+zmm[04] [03:00] = { 0xa8186014 0xa8186013 0xa8186012 0xa8186011 }
+zmm[04] [07:04] = { 0x28186049 0x28186048 0x28186047 0x28186046 }
+zmm[04] [11:08] = { 0x2818604d 0x2818604c 0x2818604b 0x2818604a }
+zmm[04] [15:12] = { 0x28186051 0x28186050 0x2818604f 0x2818604e }
+zmm[05] [03:00] = { 0xa8186018 0xa8186017 0xa8186016 0xa8186015 }
+zmm[05] [07:04] = { 0x28186059 0x28186058 0x28186057 0x28186056 }
+zmm[05] [11:08] = { 0x2818605d 0x2818605c 0x2818605b 0x2818605a }
+zmm[05] [15:12] = { 0x28186061 0x28186060 0x2818605f 0x2818605e }
+zmm[06] [03:00] = { 0xa818601c 0xa818601b 0xa818601a 0xa8186019 }
+zmm[06] [07:04] = { 0x28186069 0x28186068 0x28186067 0x28186066 }
+zmm[06] [11:08] = { 0x2818606d 0x2818606c 0x2818606b 0x2818606a }
+zmm[06] [15:12] = { 0x28186071 0x28186070 0x2818606f 0x2818606e }
+zmm[07] [03:00] = { 0xa8186020 0xa818601f 0xa818601e 0xa818601d }
+zmm[07] [07:04] = { 0x28186079 0x28186078 0x28186077 0x28186076 }
+zmm[07] [11:08] = { 0x2818607d 0x2818607c 0x2818607b 0x2818607a }
+zmm[07] [15:12] = { 0x28186081 0x28186080 0x2818607f 0x2818607e }
+zmm[08] [03:00] = { 0xa8186024 0xa8186023 0xa8186022 0xa8186021 }
+zmm[08] [07:04] = { 0x28186089 0x28186088 0x28186087 0x28186086 }
+zmm[08] [11:08] = { 0x2818608d 0x2818608c 0x2818608b 0x2818608a }
+zmm[08] [15:12] = { 0x28186091 0x28186090 0x2818608f 0x2818608e }
+zmm[09] [03:00] = { 0xa8186028 0xa8186027 0xa8186026 0xa8186025 }
+zmm[09] [07:04] = { 0x28186099 0x28186098 0x28186097 0x28186096 }
+zmm[09] [11:08] = { 0x2818609d 0x2818609c 0x2818609b 0x2818609a }
+zmm[09] [15:12] = { 0x281860a1 0x281860a0 0x2818609f 0x2818609e }
+zmm[10] [03:00] = { 0xa818602c 0xa818602b 0xa818602a 0xa8186029 }
+zmm[10] [07:04] = { 0x281860a9 0x281860a8 0x281860a7 0x281860a6 }
+zmm[10] [11:08] = { 0x281860ad 0x281860ac 0x281860ab 0x281860aa }
+zmm[10] [15:12] = { 0x281860b1 0x281860b0 0x281860af 0x281860ae }
+zmm[11] [03:00] = { 0xa8186030 0xa818602f 0xa818602e 0xa818602d }
+zmm[11] [07:04] = { 0x281860b9 0x281860b8 0x281860b7 0x281860b6 }
+zmm[11] [11:08] = { 0x281860bd 0x281860bc 0x281860bb 0x281860ba }
+zmm[11] [15:12] = { 0x281860c1 0x281860c0 0x281860bf 0x281860be }
+zmm[12] [03:00] = { 0xa8186034 0xa8186033 0xa8186032 0xa8186031 }
+zmm[12] [07:04] = { 0x281860c9 0x281860c8 0x281860c7 0x281860c6 }
+zmm[12] [11:08] = { 0x281860cd 0x281860cc 0x281860cb 0x281860ca }
+zmm[12] [15:12] = { 0x281860d1 0x281860d0 0x281860cf 0x281860ce }
+zmm[13] [03:00] = { 0xa8186038 0xa8186037 0xa8186036 0xa8186035 }
+zmm[13] [07:04] = { 0x281860d9 0x281860d8 0x281860d7 0x281860d6 }
+zmm[13] [11:08] = { 0x281860dd 0x281860dc 0x281860db 0x281860da }
+zmm[13] [15:12] = { 0x281860e1 0x281860e0 0x281860df 0x281860de }
+zmm[14] [03:00] = { 0xa818603c 0xa818603b 0xa818603a 0xa8186039 }
+zmm[14] [07:04] = { 0x281860e9 0x281860e8 0x281860e7 0x281860e6 }
+zmm[14] [11:08] = { 0x281860ed 0x281860ec 0x281860eb 0x281860ea }
+zmm[14] [15:12] = { 0x281860f1 0x281860f0 0x281860ef 0x281860ee }
+zmm[15] [03:00] = { 0xa8186040 0xa818603f 0xa818603e 0xa818603d }
+zmm[15] [07:04] = { 0x281860f9 0x281860f8 0x281860f7 0x281860f6 }
+zmm[15] [11:08] = { 0x281860fd 0x281860fc 0x281860fb 0x281860fa }
+zmm[15] [15:12] = { 0x28186101 0x28186100 0x281860ff 0x281860fe }
+zmm[16] [03:00] = { 0x28186105 0x28186104 0x28186103 0x28186102 }
+zmm[16] [07:04] = { 0x28186109 0x28186108 0x28186107 0x28186106 }
+zmm[16] [11:08] = { 0x2818610d 0x2818610c 0x2818610b 0x2818610a }
+zmm[16] [15:12] = { 0x28186111 0x28186110 0x2818610f 0x2818610e }
+zmm[17] [03:00] = { 0x28186115 0x28186114 0x28186113 0x28186112 }
+zmm[17] [07:04] = { 0x28186119 0x28186118 0x28186117 0x28186116 }
+zmm[17] [11:08] = { 0x2818611d 0x2818611c 0x2818611b 0x2818611a }
+zmm[17] [15:12] = { 0x28186121 0x28186120 0x2818611f 0x2818611e }
+zmm[18] [03:00] = { 0x28186125 0x28186124 0x28186123 0x28186122 }
+zmm[18] [07:04] = { 0x28186129 0x28186128 0x28186127 0x28186126 }
+zmm[18] [11:08] = { 0x2818612d 0x2818612c 0x2818612b 0x2818612a }
+zmm[18] [15:12] = { 0x28186131 0x28186130 0x2818612f 0x2818612e }
+zmm[19] [03:00] = { 0x28186135 0x28186134 0x28186133 0x28186132 }
+zmm[19] [07:04] = { 0x28186139 0x28186138 0x28186137 0x28186136 }
+zmm[19] [11:08] = { 0x2818613d 0x2818613c 0x2818613b 0x2818613a }
+zmm[19] [15:12] = { 0x28186141 0x28186140 0x2818613f 0x2818613e }
+zmm[20] [03:00] = { 0x28186145 0x28186144 0x28186143 0x28186142 }
+zmm[20] [07:04] = { 0x28186149 0x28186148 0x28186147 0x28186146 }
+zmm[20] [11:08] = { 0x2818614d 0x2818614c 0x2818614b 0x2818614a }
+zmm[20] [15:12] = { 0x28186151 0x28186150 0x2818614f 0x2818614e }
+zmm[21] [03:00] = { 0x28186155 0x28186154 0x28186153 0x28186152 }
+zmm[21] [07:04] = { 0x28186159 0x28186158 0x28186157 0x28186156 }
+zmm[21] [11:08] = { 0x2818615d 0x2818615c 0x2818615b 0x2818615a }
+zmm[21] [15:12] = { 0x28186161 0x28186160 0x2818615f 0x2818615e }
+zmm[22] [03:00] = { 0x28186165 0x28186164 0x28186163 0x28186162 }
+zmm[22] [07:04] = { 0x28186169 0x28186168 0x28186167 0x28186166 }
+zmm[22] [11:08] = { 0x2818616d 0x2818616c 0x2818616b 0x2818616a }
+zmm[22] [15:12] = { 0x28186171 0x28186170 0x2818616f 0x2818616e }
+zmm[23] [03:00] = { 0x28186175 0x28186174 0x28186173 0x28186172 }
+zmm[23] [07:04] = { 0x28186179 0x28186178 0x28186177 0x28186176 }
+zmm[23] [11:08] = { 0x2818617d 0x2818617c 0x2818617b 0x2818617a }
+zmm[23] [15:12] = { 0x28186181 0x28186180 0x2818617f 0x2818617e }
+zmm[24] [03:00] = { 0x28186185 0x28186184 0x28186183 0x28186182 }
+zmm[24] [07:04] = { 0x28186189 0x28186188 0x28186187 0x28186186 }
+zmm[24] [11:08] = { 0x2818618d 0x2818618c 0x2818618b 0x2818618a }
+zmm[24] [15:12] = { 0x28186191 0x28186190 0x2818618f 0x2818618e }
+zmm[25] [03:00] = { 0x28186195 0x28186194 0x28186193 0x28186192 }
+zmm[25] [07:04] = { 0x28186199 0x28186198 0x28186197 0x28186196 }
+zmm[25] [11:08] = { 0x2818619d 0x2818619c 0x2818619b 0x2818619a }
+zmm[25] [15:12] = { 0x281861a1 0x281861a0 0x2818619f 0x2818619e }
+zmm[26] [03:00] = { 0x281861a5 0x281861a4 0x281861a3 0x281861a2 }
+zmm[26] [07:04] = { 0x281861a9 0x281861a8 0x281861a7 0x281861a6 }
+zmm[26] [11:08] = { 0x281861ad 0x281861ac 0x281861ab 0x281861aa }
+zmm[26] [15:12] = { 0x281861b1 0x281861b0 0x281861af 0x281861ae }
+zmm[27] [03:00] = { 0x281861b5 0x281861b4 0x281861b3 0x281861b2 }
+zmm[27] [07:04] = { 0x281861b9 0x281861b8 0x281861b7 0x281861b6 }
+zmm[27] [11:08] = { 0x281861bd 0x281861bc 0x281861bb 0x281861ba }
+zmm[27] [15:12] = { 0x281861c1 0x281861c0 0x281861bf 0x281861be }
+zmm[28] [03:00] = { 0x281861c5 0x281861c4 0x281861c3 0x281861c2 }
+zmm[28] [07:04] = { 0x281861c9 0x281861c8 0x281861c7 0x281861c6 }
+zmm[28] [11:08] = { 0x281861cd 0x281861cc 0x281861cb 0x281861ca }
+zmm[28] [15:12] = { 0x281861d1 0x281861d0 0x281861cf 0x281861ce }
+zmm[29] [03:00] = { 0x281861d5 0x281861d4 0x281861d3 0x281861d2 }
+zmm[29] [07:04] = { 0x281861d9 0x281861d8 0x281861d7 0x281861d6 }
+zmm[29] [11:08] = { 0x281861dd 0x281861dc 0x281861db 0x281861da }
+zmm[29] [15:12] = { 0x281861e1 0x281861e0 0x281861df 0x281861de }
+zmm[30] [03:00] = { 0x281861e5 0x281861e4 0x281861e3 0x281861e2 }
+zmm[30] [07:04] = { 0x281861e9 0x281861e8 0x281861e7 0x281861e6 }
+zmm[30] [11:08] = { 0x281861ed 0x281861ec 0x281861eb 0x281861ea }
+zmm[30] [15:12] = { 0x281861f1 0x281861f0 0x281861ef 0x281861ee }
+zmm[31] [03:00] = { 0x281861f5 0x281861f4 0x281861f3 0x281861f2 }
+zmm[31] [07:04] = { 0x281861f9 0x281861f8 0x281861f7 0x281861f6 }
+zmm[31] [11:08] = { 0x281861fd 0x281861fc 0x281861fb 0x281861fa }
+zmm[31] [15:12] = { 0x28186201 0x28186200 0x281861ff 0x281861fe }
+%k0 0x2818620328186202
+%k1 0x2818620528186204
+%k2 0x2818620728186206
+%k3 0x2818620928186208
+%k4 0x2818620b2818620a
+%k5 0x2818620d2818620c
+%k6 0x2818620f2818620e
+%k7 0x2818621128186210
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.xregs.ymm.32	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,16 @@
+ymm[00] [03:00] = { 0xc120ff09 0xc120ff08 0xc120ff07 0xc120ff06 }
+ymm[00] [07:04] = { 0xc120ff0d 0xc120ff0c 0xc120ff0b 0xc120ff0a }
+ymm[01] [03:00] = { 0xc120ff11 0xc120ff10 0xc120ff0f 0xc120ff0e }
+ymm[01] [07:04] = { 0xc120ff15 0xc120ff14 0xc120ff13 0xc120ff12 }
+ymm[02] [03:00] = { 0xc120ff19 0xc120ff18 0xc120ff17 0xc120ff16 }
+ymm[02] [07:04] = { 0xc120ff1d 0xc120ff1c 0xc120ff1b 0xc120ff1a }
+ymm[03] [03:00] = { 0xc120ff21 0xc120ff20 0xc120ff1f 0xc120ff1e }
+ymm[03] [07:04] = { 0xc120ff25 0xc120ff24 0xc120ff23 0xc120ff22 }
+ymm[04] [03:00] = { 0xc120ff29 0xc120ff28 0xc120ff27 0xc120ff26 }
+ymm[04] [07:04] = { 0xc120ff2d 0xc120ff2c 0xc120ff2b 0xc120ff2a }
+ymm[05] [03:00] = { 0xc120ff31 0xc120ff30 0xc120ff2f 0xc120ff2e }
+ymm[05] [07:04] = { 0xc120ff35 0xc120ff34 0xc120ff33 0xc120ff32 }
+ymm[06] [03:00] = { 0xc120ff39 0xc120ff38 0xc120ff37 0xc120ff36 }
+ymm[06] [07:04] = { 0xc120ff3d 0xc120ff3c 0xc120ff3b 0xc120ff3a }
+ymm[07] [03:00] = { 0xc120ff41 0xc120ff40 0xc120ff3f 0xc120ff3e }
+ymm[07] [07:04] = { 0xc120ff45 0xc120ff44 0xc120ff43 0xc120ff42 }
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.xregs.ymm.64	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,32 @@
+ymm[00] [03:00] = { 0xfaceecb2 0xfaceecb1 0xfaceecb0 0xfaceecaf }
+ymm[00] [07:04] = { 0xfaceecb6 0xfaceecb5 0xfaceecb4 0xfaceecb3 }
+ymm[01] [03:00] = { 0xfaceecba 0xfaceecb9 0xfaceecb8 0xfaceecb7 }
+ymm[01] [07:04] = { 0xfaceecbe 0xfaceecbd 0xfaceecbc 0xfaceecbb }
+ymm[02] [03:00] = { 0xfaceecc2 0xfaceecc1 0xfaceecc0 0xfaceecbf }
+ymm[02] [07:04] = { 0xfaceecc6 0xfaceecc5 0xfaceecc4 0xfaceecc3 }
+ymm[03] [03:00] = { 0xfaceecca 0xfaceecc9 0xfaceecc8 0xfaceecc7 }
+ymm[03] [07:04] = { 0xfaceecce 0xfaceeccd 0xfaceeccc 0xfaceeccb }
+ymm[04] [03:00] = { 0xfaceecd2 0xfaceecd1 0xfaceecd0 0xfaceeccf }
+ymm[04] [07:04] = { 0xfaceecd6 0xfaceecd5 0xfaceecd4 0xfaceecd3 }
+ymm[05] [03:00] = { 0xfaceecda 0xfaceecd9 0xfaceecd8 0xfaceecd7 }
+ymm[05] [07:04] = { 0xfaceecde 0xfaceecdd 0xfaceecdc 0xfaceecdb }
+ymm[06] [03:00] = { 0xfaceece2 0xfaceece1 0xfaceece0 0xfaceecdf }
+ymm[06] [07:04] = { 0xfaceece6 0xfaceece5 0xfaceece4 0xfaceece3 }
+ymm[07] [03:00] = { 0xfaceecea 0xfaceece9 0xfaceece8 0xfaceece7 }
+ymm[07] [07:04] = { 0xfaceecee 0xfaceeced 0xfaceecec 0xfaceeceb }
+ymm[08] [03:00] = { 0xfaceecf2 0xfaceecf1 0xfaceecf0 0xfaceecef }
+ymm[08] [07:04] = { 0xfaceecf6 0xfaceecf5 0xfaceecf4 0xfaceecf3 }
+ymm[09] [03:00] = { 0xfaceecfa 0xfaceecf9 0xfaceecf8 0xfaceecf7 }
+ymm[09] [07:04] = { 0xfaceecfe 0xfaceecfd 0xfaceecfc 0xfaceecfb }
+ymm[10] [03:00] = { 0xfaceed02 0xfaceed01 0xfaceed00 0xfaceecff }
+ymm[10] [07:04] = { 0xfaceed06 0xfaceed05 0xfaceed04 0xfaceed03 }
+ymm[11] [03:00] = { 0xfaceed0a 0xfaceed09 0xfaceed08 0xfaceed07 }
+ymm[11] [07:04] = { 0xfaceed0e 0xfaceed0d 0xfaceed0c 0xfaceed0b }
+ymm[12] [03:00] = { 0xfaceed12 0xfaceed11 0xfaceed10 0xfaceed0f }
+ymm[12] [07:04] = { 0xfaceed16 0xfaceed15 0xfaceed14 0xfaceed13 }
+ymm[13] [03:00] = { 0xfaceed1a 0xfaceed19 0xfaceed18 0xfaceed17 }
+ymm[13] [07:04] = { 0xfaceed1e 0xfaceed1d 0xfaceed1c 0xfaceed1b }
+ymm[14] [03:00] = { 0xfaceed22 0xfaceed21 0xfaceed20 0xfaceed1f }
+ymm[14] [07:04] = { 0xfaceed26 0xfaceed25 0xfaceed24 0xfaceed23 }
+ymm[15] [03:00] = { 0xfaceed2a 0xfaceed29 0xfaceed28 0xfaceed27 }
+ymm[15] [07:04] = { 0xfaceed2e 0xfaceed2d 0xfaceed2c 0xfaceed2b }
--- /dev/null	Wed Apr 12 20:33:28 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.xregs.zmm.32	Wed Apr 12 20:33:28 2023
@@ -0,0 +1,40 @@
+zmm[00] [03:00] = { 0xc120ff09 0xc120ff08 0xc120ff07 0xc120ff06 }
+zmm[00] [07:04] = { 0xc120ff0d 0xc120ff0c 0xc120ff0b 0xc120ff0a }
+zmm[00] [11:08] = { 0xc120ff11 0xc120ff10 0xc120ff0f 0xc120ff0e }
+zmm[00] [15:12] = { 0xc120ff15 0xc120ff14 0xc120ff13 0xc120ff12 }
+zmm[01] [03:00] = { 0xc120ff19 0xc120ff18 0xc120ff17 0xc120ff16 }
+zmm[01] [07:04] = { 0xc120ff1d 0xc120ff1c 0xc120ff1b 0xc120ff1a }
+zmm[01] [11:08] = { 0xc120ff21 0xc120ff20 0xc120ff1f 0xc120ff1e }
+zmm[01] [15:12] = { 0xc120ff25 0xc120ff24 0xc120ff23 0xc120ff22 }
+zmm[02] [03:00] = { 0xc120ff29 0xc120ff28 0xc120ff27 0xc120ff26 }
+zmm[02] [07:04] = { 0xc120ff2d 0xc120ff2c 0xc120ff2b 0xc120ff2a }
+zmm[02] [11:08] = { 0xc120ff31 0xc120ff30 0xc120ff2f 0xc120ff2e }
+zmm[02] [15:12] = { 0xc120ff35 0xc120ff34 0xc120ff33 0xc120ff32 }
+zmm[03] [03:00] = { 0xc120ff39 0xc120ff38 0xc120ff37 0xc120ff36 }
+zmm[03] [07:04] = { 0xc120ff3d 0xc120ff3c 0xc120ff3b 0xc120ff3a }
+zmm[03] [11:08] = { 0xc120ff41 0xc120ff40 0xc120ff3f 0xc120ff3e }
+zmm[03] [15:12] = { 0xc120ff45 0xc120ff44 0xc120ff43 0xc120ff42 }
+zmm[04] [03:00] = { 0xc120ff49 0xc120ff48 0xc120ff47 0xc120ff46 }
+zmm[04] [07:04] = { 0xc120ff4d 0xc120ff4c 0xc120ff4b 0xc120ff4a }
+zmm[04] [11:08] = { 0xc120ff51 0xc120ff50 0xc120ff4f 0xc120ff4e }
+zmm[04] [15:12] = { 0xc120ff55 0xc120ff54 0xc120ff53 0xc120ff52 }
+zmm[05] [03:00] = { 0xc120ff59 0xc120ff58 0xc120ff57 0xc120ff56 }
+zmm[05] [07:04] = { 0xc120ff5d 0xc120ff5c 0xc120ff5b 0xc120ff5a }
+zmm[05] [11:08] = { 0xc120ff61 0xc120ff60 0xc120ff5f 0xc120ff5e }
+zmm[05] [15:12] = { 0xc120ff65 0xc120ff64 0xc120ff63 0xc120ff62 }
+zmm[06] [03:00] = { 0xc120ff69 0xc120ff68 0xc120ff67 0xc120ff66 }
+zmm[06] [07:04] = { 0xc120ff6d 0xc120ff6c 0xc120ff6b 0xc120ff6a }
+zmm[06] [11:08] = { 0xc120ff71 0xc120ff70 0xc120ff6f 0xc120ff6e }
+zmm[06] [15:12] = { 0xc120ff75 0xc120ff74 0xc120ff73 0xc120ff72 }
+zmm[07] [03:00] = { 0xc120ff79 0xc120ff78 0xc120ff77 0xc120ff76 }
+zmm[07] [07:04] = { 0xc120ff7d 0xc120ff7c 0xc120ff7b 0xc120ff7a }
+zmm[07] [11:08] = { 0xc120ff81 0xc120ff80 0xc120ff7f 0xc120ff7e }
+zmm[07] [15:12] = { 0xc120ff85 0xc120ff84 0xc120ff83 0xc120ff82 }
+%k0 0xc120ff87c120ff86
+%k1 0xc120ff89c120ff88
+%k2 0xc120ff8bc120ff8a
+%k3 0xc120ff8dc120ff8c
+%k4 0xc120ff8fc120ff8e
+%k5 0xc120ff91c120ff90
+%k6 0xc120ff93c120ff92
+%k7 0xc120ff95c120ff94
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/data/proc_writes.xregs.zmm.64	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,136 @@
+zmm[00] [03:00] = { 0xfaceecb2 0xfaceecb1 0xfaceecb0 0xfaceecaf }
+zmm[00] [07:04] = { 0xfaceecb6 0xfaceecb5 0xfaceecb4 0xfaceecb3 }
+zmm[00] [11:08] = { 0xfaceecba 0xfaceecb9 0xfaceecb8 0xfaceecb7 }
+zmm[00] [15:12] = { 0xfaceecbe 0xfaceecbd 0xfaceecbc 0xfaceecbb }
+zmm[01] [03:00] = { 0xfaceecc2 0xfaceecc1 0xfaceecc0 0xfaceecbf }
+zmm[01] [07:04] = { 0xfaceecc6 0xfaceecc5 0xfaceecc4 0xfaceecc3 }
+zmm[01] [11:08] = { 0xfaceecca 0xfaceecc9 0xfaceecc8 0xfaceecc7 }
+zmm[01] [15:12] = { 0xfaceecce 0xfaceeccd 0xfaceeccc 0xfaceeccb }
+zmm[02] [03:00] = { 0xfaceecd2 0xfaceecd1 0xfaceecd0 0xfaceeccf }
+zmm[02] [07:04] = { 0xfaceecd6 0xfaceecd5 0xfaceecd4 0xfaceecd3 }
+zmm[02] [11:08] = { 0xfaceecda 0xfaceecd9 0xfaceecd8 0xfaceecd7 }
+zmm[02] [15:12] = { 0xfaceecde 0xfaceecdd 0xfaceecdc 0xfaceecdb }
+zmm[03] [03:00] = { 0xfaceece2 0xfaceece1 0xfaceece0 0xfaceecdf }
+zmm[03] [07:04] = { 0xfaceece6 0xfaceece5 0xfaceece4 0xfaceece3 }
+zmm[03] [11:08] = { 0xfaceecea 0xfaceece9 0xfaceece8 0xfaceece7 }
+zmm[03] [15:12] = { 0xfaceecee 0xfaceeced 0xfaceecec 0xfaceeceb }
+zmm[04] [03:00] = { 0xfaceecf2 0xfaceecf1 0xfaceecf0 0xfaceecef }
+zmm[04] [07:04] = { 0xfaceecf6 0xfaceecf5 0xfaceecf4 0xfaceecf3 }
+zmm[04] [11:08] = { 0xfaceecfa 0xfaceecf9 0xfaceecf8 0xfaceecf7 }
+zmm[04] [15:12] = { 0xfaceecfe 0xfaceecfd 0xfaceecfc 0xfaceecfb }
+zmm[05] [03:00] = { 0xfaceed02 0xfaceed01 0xfaceed00 0xfaceecff }
+zmm[05] [07:04] = { 0xfaceed06 0xfaceed05 0xfaceed04 0xfaceed03 }
+zmm[05] [11:08] = { 0xfaceed0a 0xfaceed09 0xfaceed08 0xfaceed07 }
+zmm[05] [15:12] = { 0xfaceed0e 0xfaceed0d 0xfaceed0c 0xfaceed0b }
+zmm[06] [03:00] = { 0xfaceed12 0xfaceed11 0xfaceed10 0xfaceed0f }
+zmm[06] [07:04] = { 0xfaceed16 0xfaceed15 0xfaceed14 0xfaceed13 }
+zmm[06] [11:08] = { 0xfaceed1a 0xfaceed19 0xfaceed18 0xfaceed17 }
+zmm[06] [15:12] = { 0xfaceed1e 0xfaceed1d 0xfaceed1c 0xfaceed1b }
+zmm[07] [03:00] = { 0xfaceed22 0xfaceed21 0xfaceed20 0xfaceed1f }
+zmm[07] [07:04] = { 0xfaceed26 0xfaceed25 0xfaceed24 0xfaceed23 }
+zmm[07] [11:08] = { 0xfaceed2a 0xfaceed29 0xfaceed28 0xfaceed27 }
+zmm[07] [15:12] = { 0xfaceed2e 0xfaceed2d 0xfaceed2c 0xfaceed2b }
+zmm[08] [03:00] = { 0xfaceed32 0xfaceed31 0xfaceed30 0xfaceed2f }
+zmm[08] [07:04] = { 0xfaceed36 0xfaceed35 0xfaceed34 0xfaceed33 }
+zmm[08] [11:08] = { 0xfaceed3a 0xfaceed39 0xfaceed38 0xfaceed37 }
+zmm[08] [15:12] = { 0xfaceed3e 0xfaceed3d 0xfaceed3c 0xfaceed3b }
+zmm[09] [03:00] = { 0xfaceed42 0xfaceed41 0xfaceed40 0xfaceed3f }
+zmm[09] [07:04] = { 0xfaceed46 0xfaceed45 0xfaceed44 0xfaceed43 }
+zmm[09] [11:08] = { 0xfaceed4a 0xfaceed49 0xfaceed48 0xfaceed47 }
+zmm[09] [15:12] = { 0xfaceed4e 0xfaceed4d 0xfaceed4c 0xfaceed4b }
+zmm[10] [03:00] = { 0xfaceed52 0xfaceed51 0xfaceed50 0xfaceed4f }
+zmm[10] [07:04] = { 0xfaceed56 0xfaceed55 0xfaceed54 0xfaceed53 }
+zmm[10] [11:08] = { 0xfaceed5a 0xfaceed59 0xfaceed58 0xfaceed57 }
+zmm[10] [15:12] = { 0xfaceed5e 0xfaceed5d 0xfaceed5c 0xfaceed5b }
+zmm[11] [03:00] = { 0xfaceed62 0xfaceed61 0xfaceed60 0xfaceed5f }
+zmm[11] [07:04] = { 0xfaceed66 0xfaceed65 0xfaceed64 0xfaceed63 }
+zmm[11] [11:08] = { 0xfaceed6a 0xfaceed69 0xfaceed68 0xfaceed67 }
+zmm[11] [15:12] = { 0xfaceed6e 0xfaceed6d 0xfaceed6c 0xfaceed6b }
+zmm[12] [03:00] = { 0xfaceed72 0xfaceed71 0xfaceed70 0xfaceed6f }
+zmm[12] [07:04] = { 0xfaceed76 0xfaceed75 0xfaceed74 0xfaceed73 }
+zmm[12] [11:08] = { 0xfaceed7a 0xfaceed79 0xfaceed78 0xfaceed77 }
+zmm[12] [15:12] = { 0xfaceed7e 0xfaceed7d 0xfaceed7c 0xfaceed7b }
+zmm[13] [03:00] = { 0xfaceed82 0xfaceed81 0xfaceed80 0xfaceed7f }
+zmm[13] [07:04] = { 0xfaceed86 0xfaceed85 0xfaceed84 0xfaceed83 }
+zmm[13] [11:08] = { 0xfaceed8a 0xfaceed89 0xfaceed88 0xfaceed87 }
+zmm[13] [15:12] = { 0xfaceed8e 0xfaceed8d 0xfaceed8c 0xfaceed8b }
+zmm[14] [03:00] = { 0xfaceed92 0xfaceed91 0xfaceed90 0xfaceed8f }
+zmm[14] [07:04] = { 0xfaceed96 0xfaceed95 0xfaceed94 0xfaceed93 }
+zmm[14] [11:08] = { 0xfaceed9a 0xfaceed99 0xfaceed98 0xfaceed97 }
+zmm[14] [15:12] = { 0xfaceed9e 0xfaceed9d 0xfaceed9c 0xfaceed9b }
+zmm[15] [03:00] = { 0xfaceeda2 0xfaceeda1 0xfaceeda0 0xfaceed9f }
+zmm[15] [07:04] = { 0xfaceeda6 0xfaceeda5 0xfaceeda4 0xfaceeda3 }
+zmm[15] [11:08] = { 0xfaceedaa 0xfaceeda9 0xfaceeda8 0xfaceeda7 }
+zmm[15] [15:12] = { 0xfaceedae 0xfaceedad 0xfaceedac 0xfaceedab }
+zmm[16] [03:00] = { 0xfaceedb2 0xfaceedb1 0xfaceedb0 0xfaceedaf }
+zmm[16] [07:04] = { 0xfaceedb6 0xfaceedb5 0xfaceedb4 0xfaceedb3 }
+zmm[16] [11:08] = { 0xfaceedba 0xfaceedb9 0xfaceedb8 0xfaceedb7 }
+zmm[16] [15:12] = { 0xfaceedbe 0xfaceedbd 0xfaceedbc 0xfaceedbb }
+zmm[17] [03:00] = { 0xfaceedc2 0xfaceedc1 0xfaceedc0 0xfaceedbf }
+zmm[17] [07:04] = { 0xfaceedc6 0xfaceedc5 0xfaceedc4 0xfaceedc3 }
+zmm[17] [11:08] = { 0xfaceedca 0xfaceedc9 0xfaceedc8 0xfaceedc7 }
+zmm[17] [15:12] = { 0xfaceedce 0xfaceedcd 0xfaceedcc 0xfaceedcb }
+zmm[18] [03:00] = { 0xfaceedd2 0xfaceedd1 0xfaceedd0 0xfaceedcf }
+zmm[18] [07:04] = { 0xfaceedd6 0xfaceedd5 0xfaceedd4 0xfaceedd3 }
+zmm[18] [11:08] = { 0xfaceedda 0xfaceedd9 0xfaceedd8 0xfaceedd7 }
+zmm[18] [15:12] = { 0xfaceedde 0xfaceeddd 0xfaceeddc 0xfaceeddb }
+zmm[19] [03:00] = { 0xfaceede2 0xfaceede1 0xfaceede0 0xfaceeddf }
+zmm[19] [07:04] = { 0xfaceede6 0xfaceede5 0xfaceede4 0xfaceede3 }
+zmm[19] [11:08] = { 0xfaceedea 0xfaceede9 0xfaceede8 0xfaceede7 }
+zmm[19] [15:12] = { 0xfaceedee 0xfaceeded 0xfaceedec 0xfaceedeb }
+zmm[20] [03:00] = { 0xfaceedf2 0xfaceedf1 0xfaceedf0 0xfaceedef }
+zmm[20] [07:04] = { 0xfaceedf6 0xfaceedf5 0xfaceedf4 0xfaceedf3 }
+zmm[20] [11:08] = { 0xfaceedfa 0xfaceedf9 0xfaceedf8 0xfaceedf7 }
+zmm[20] [15:12] = { 0xfaceedfe 0xfaceedfd 0xfaceedfc 0xfaceedfb }
+zmm[21] [03:00] = { 0xfaceee02 0xfaceee01 0xfaceee00 0xfaceedff }
+zmm[21] [07:04] = { 0xfaceee06 0xfaceee05 0xfaceee04 0xfaceee03 }
+zmm[21] [11:08] = { 0xfaceee0a 0xfaceee09 0xfaceee08 0xfaceee07 }
+zmm[21] [15:12] = { 0xfaceee0e 0xfaceee0d 0xfaceee0c 0xfaceee0b }
+zmm[22] [03:00] = { 0xfaceee12 0xfaceee11 0xfaceee10 0xfaceee0f }
+zmm[22] [07:04] = { 0xfaceee16 0xfaceee15 0xfaceee14 0xfaceee13 }
+zmm[22] [11:08] = { 0xfaceee1a 0xfaceee19 0xfaceee18 0xfaceee17 }
+zmm[22] [15:12] = { 0xfaceee1e 0xfaceee1d 0xfaceee1c 0xfaceee1b }
+zmm[23] [03:00] = { 0xfaceee22 0xfaceee21 0xfaceee20 0xfaceee1f }
+zmm[23] [07:04] = { 0xfaceee26 0xfaceee25 0xfaceee24 0xfaceee23 }
+zmm[23] [11:08] = { 0xfaceee2a 0xfaceee29 0xfaceee28 0xfaceee27 }
+zmm[23] [15:12] = { 0xfaceee2e 0xfaceee2d 0xfaceee2c 0xfaceee2b }
+zmm[24] [03:00] = { 0xfaceee32 0xfaceee31 0xfaceee30 0xfaceee2f }
+zmm[24] [07:04] = { 0xfaceee36 0xfaceee35 0xfaceee34 0xfaceee33 }
+zmm[24] [11:08] = { 0xfaceee3a 0xfaceee39 0xfaceee38 0xfaceee37 }
+zmm[24] [15:12] = { 0xfaceee3e 0xfaceee3d 0xfaceee3c 0xfaceee3b }
+zmm[25] [03:00] = { 0xfaceee42 0xfaceee41 0xfaceee40 0xfaceee3f }
+zmm[25] [07:04] = { 0xfaceee46 0xfaceee45 0xfaceee44 0xfaceee43 }
+zmm[25] [11:08] = { 0xfaceee4a 0xfaceee49 0xfaceee48 0xfaceee47 }
+zmm[25] [15:12] = { 0xfaceee4e 0xfaceee4d 0xfaceee4c 0xfaceee4b }
+zmm[26] [03:00] = { 0xfaceee52 0xfaceee51 0xfaceee50 0xfaceee4f }
+zmm[26] [07:04] = { 0xfaceee56 0xfaceee55 0xfaceee54 0xfaceee53 }
+zmm[26] [11:08] = { 0xfaceee5a 0xfaceee59 0xfaceee58 0xfaceee57 }
+zmm[26] [15:12] = { 0xfaceee5e 0xfaceee5d 0xfaceee5c 0xfaceee5b }
+zmm[27] [03:00] = { 0xfaceee62 0xfaceee61 0xfaceee60 0xfaceee5f }
+zmm[27] [07:04] = { 0xfaceee66 0xfaceee65 0xfaceee64 0xfaceee63 }
+zmm[27] [11:08] = { 0xfaceee6a 0xfaceee69 0xfaceee68 0xfaceee67 }
+zmm[27] [15:12] = { 0xfaceee6e 0xfaceee6d 0xfaceee6c 0xfaceee6b }
+zmm[28] [03:00] = { 0xfaceee72 0xfaceee71 0xfaceee70 0xfaceee6f }
+zmm[28] [07:04] = { 0xfaceee76 0xfaceee75 0xfaceee74 0xfaceee73 }
+zmm[28] [11:08] = { 0xfaceee7a 0xfaceee79 0xfaceee78 0xfaceee77 }
+zmm[28] [15:12] = { 0xfaceee7e 0xfaceee7d 0xfaceee7c 0xfaceee7b }
+zmm[29] [03:00] = { 0xfaceee82 0xfaceee81 0xfaceee80 0xfaceee7f }
+zmm[29] [07:04] = { 0xfaceee86 0xfaceee85 0xfaceee84 0xfaceee83 }
+zmm[29] [11:08] = { 0xfaceee8a 0xfaceee89 0xfaceee88 0xfaceee87 }
+zmm[29] [15:12] = { 0xfaceee8e 0xfaceee8d 0xfaceee8c 0xfaceee8b }
+zmm[30] [03:00] = { 0xfaceee92 0xfaceee91 0xfaceee90 0xfaceee8f }
+zmm[30] [07:04] = { 0xfaceee96 0xfaceee95 0xfaceee94 0xfaceee93 }
+zmm[30] [11:08] = { 0xfaceee9a 0xfaceee99 0xfaceee98 0xfaceee97 }
+zmm[30] [15:12] = { 0xfaceee9e 0xfaceee9d 0xfaceee9c 0xfaceee9b }
+zmm[31] [03:00] = { 0xfaceeea2 0xfaceeea1 0xfaceeea0 0xfaceee9f }
+zmm[31] [07:04] = { 0xfaceeea6 0xfaceeea5 0xfaceeea4 0xfaceeea3 }
+zmm[31] [11:08] = { 0xfaceeeaa 0xfaceeea9 0xfaceeea8 0xfaceeea7 }
+zmm[31] [15:12] = { 0xfaceeeae 0xfaceeead 0xfaceeeac 0xfaceeeab }
+%k0 0xfaceeeb0faceeeaf
+%k1 0xfaceeeb2faceeeb1
+%k2 0xfaceeeb4faceeeb3
+%k3 0xfaceeeb6faceeeb5
+%k4 0xfaceeeb8faceeeb7
+%k5 0xfaceeebafaceeeb9
+%k6 0xfaceeebcfaceeebb
+%k7 0xfaceeebefaceeebd
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/fpregs.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,130 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This test verifies the following:
+ *
+ *   o xregs and fpregs report the same content for the xmm registers at least.
+ *   o A write to xregs is reflected in reads of fpregs.
+ *   o A write to the fpregs is reflected in reads of xregs and doesn't
+ *     clobber additional state in xregs.
+ *   o A thread in our victim process sees the final state here and can print
+ *     that out.
+ *   o As a side effect it makes sure that libproc isn't incorrectly caching
+ *     register info on handles.
+ *
+ * We use the xsu_dump process of the same bitness as us.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "xsave_util.h"
+
+static xsu_fpu_t fpu;
+
+int
+main(int argc, char *argv[])
+{
+	uint32_t seed, hwsup;
+	unsigned long ul;
+	char *eptr;
+	prxregset_t *prx, *cmp_prx;
+	size_t prx_len, cmp_prx_len;
+	xsu_proc_t xp;
+	fpregset_t fpr;
+
+	if (argc != 5) {
+		errx(EXIT_FAILURE, "missing args: <prog> <output file> "
+		    "<seed> <func>");
+	}
+
+	errno = 0;
+	ul = strtoul(argv[3], &eptr, 0);
+	if (errno != 0 || *eptr != '\0') {
+		errx(EXIT_FAILURE, "seed value is bad: %s", argv[3]);
+	}
+
+#if defined(_LP64)
+	if (ul > UINT32_MAX) {
+		errx(EXIT_FAILURE, "seed %s, exceeds [0, UINT32_MAX]", argv[3]);
+	}
+#endif
+
+	seed = (uint32_t)ul;
+	hwsup = xsu_hwsupport();
+	xsu_fill(&fpu, hwsup, seed);
+	xsu_fpu_to_xregs(&fpu, hwsup, &prx, &prx_len);
+
+	(void) memset(&xp, 0, sizeof (xsu_proc_t));
+	xp.xp_prog = argv[1];
+	xp.xp_arg = argv[2];
+	xp.xp_object = "a.out";
+	xp.xp_symname = argv[4];
+	xsu_proc_bkpt(&xp);
+
+	/*
+	 * First get the xregs into a reasonable place.
+	 */
+	if (Plwp_setxregs(xp.xp_proc, 1, prx, prx_len) != 0) {
+		err(EXIT_FAILURE, "failed to set target's xregs");
+	}
+
+	/*
+	 * Now that we have that, let's go and get the fpregs. Because of
+	 * differences between the 32-bit representation and the xsave state in
+	 * the xregs, we stick to different checking in an ILP32 vs. LP64 pieces
+	 * of this.
+	 */
+	if (Plwp_getfpregs(xp.xp_proc, 1, &fpr) != 0) {
+		err(EXIT_FAILURE, "failed to get the fp registers");
+	}
+
+	if (!xsu_fpregs_cmp(&fpr, prx)) {
+		errx(EXIT_FAILURE, "fpregs do not reflect xsave changes!");
+	}
+	(void) printf("TEST PASSED: fpregs read respects xregs write\n");
+
+	/*
+	 * Override the xmm registers with the known variant of the seed and set
+	 * that. Update the xregs data so we can later compare them usefully.
+	 */
+	xsu_fpregset_xmm_set(&fpr, seed + INT32_MAX);
+	xsu_xregs_xmm_set(prx, seed + INT32_MAX);
+	if (Plwp_setfpregs(xp.xp_proc, 1, &fpr) != 0) {
+		err(EXIT_FAILURE, "failed to set fpregs");
+	}
+
+	if (Plwp_getxregs(xp.xp_proc, 1, &cmp_prx, &cmp_prx_len) != 0) {
+		err(EXIT_FAILURE, "failed to get comparison xregs");
+	}
+
+	if (!xsu_fpregs_cmp(&fpr, cmp_prx)) {
+		errx(EXIT_FAILURE, "fpregs do not reflect xsave changes!");
+	}
+	(void) printf("TEST PASSED: xregs read respects fpregs write\n");
+
+	if (!xsu_xregs_comp_equal(prx, cmp_prx, PRX_INFO_YMM)) {
+		errx(EXIT_FAILURE, "%%ymm state changed across fpregs write");
+	}
+	(void) printf("TEST PASSED: fpregs did not change other xregs "
+	    "components\n");
+
+	xsu_proc_finish(&xp);
+
+	return (EXIT_SUCCESS);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/fpregs_xbv.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,81 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * In illumos#15367 we noted that on some CPUs it was possible to get the xsave
+ * state into a place where it has not set components that are used for the x87
+ * and XMM state. This test attempts to create those situations and then get a
+ * ucontext_t to see that we actually have cleared it out. Because this behavior
+ * varies from CPU to CPU (e.g. in the original case we only saw this on AMD and
+ * not Intel), it can be tricky to guarantee we've recreated this.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <ucontext.h>
+#include <stdlib.h>
+
+int
+main(void)
+{
+	ucontext_t ctx;
+	upad128_t u, *up;
+
+	u._l[0] = 0x1;
+	u._l[1] = 0x2;
+	u._l[2] = 0x3;
+	u._l[3] = 0x4;
+
+	/*
+	 * Load %xmm0 and then lock in the data into the pcb with a call to
+	 * getcontext(2) which will force an FPU save.
+	 */
+	/* BEGIN CSTYLED */
+	__asm__ __volatile__ ("movdqu %0, %%xmm0" : : "m" (u));
+	__asm__ __volatile__ ("fldl %0" : : "m" (u));
+	/* END CSTYLED */
+	if (getcontext(&ctx) != 0) {
+		errx(EXIT_FAILURE, "TEST_FAILED: failed to get initial "
+		    "ucontext");
+	}
+
+	/*
+	 * Attempt to reset the FPU at this point and then call getcontext. The
+	 * fninit is for the x87 part. The vzeroall covers all the higher
+	 * registers and this combined should reset the x87 and xmm regions back
+	 * to 0. It appears that on some Intel processors, the vzeroall is
+	 * required to get the XMM set to not be written out as opposed to just
+	 * doing a pxor or similar.
+	 */
+	/* BEGIN CSTYLED */
+        __asm__ __volatile__ ("fninit" : : :);
+        __asm__ __volatile__ ("vzeroall" : : :);
+	/* END CSTYLED */
+	if (getcontext(&ctx) != 0) {
+		errx(EXIT_FAILURE, "TEST_FAILED: failed to get second "
+		    "ucontext");
+	}
+	up = &ctx.uc_mcontext.fpregs.fp_reg_set.fpchip_state.xmm[0];
+	if (up->_l[0] != 0 || up->_l[1] != 0 || up->_l[2] != 0 ||
+	    up->_l[3] != 0) {
+		errx(EXIT_FAILURE, "TEST FAILED: %%xmm0 was not zero, found: "
+		    "0x%x 0x%x 0x%x 0x%x", up->_l[3], up->_l[2],
+		    up->_l[1], up->_l[0]);
+	}
+
+	(void) printf("TEST PASSED: successfully got zeored %%xmm0\n");
+	return (EXIT_SUCCESS);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/mdb_xregs.ksh	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,190 @@
+#!/usr/bin/ksh
+#
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2023 Oxide Computer Company
+#
+
+#
+# This test attempts to verify that we can mdb (via libproc / thread_db)
+# and xregs sees the expected extended register set. It also ensures
+# that the same values are visible via a core file from that process at
+# the same point.
+#
+
+unalias -a
+set -o pipefail
+export LANG=C.UTF-8
+
+mx_exit=0
+mx_arg0="$(basename $0)"
+mx_dir="$(dirname $0)"
+mx_data="$mx_dir/data"
+mx_tmpdir="/tmp/mdb_xregs.$$"
+
+typeset -A mx_seed
+mx_seed["32"]=0x12900922
+mx_seed["64"]=0x11900456
+typeset -A mx_hwtype
+mx_hwtype["32"]="$mx_dir/xsu_hwtype.32"
+mx_hwtype["64"]="$mx_dir/xsu_hwtype.64"
+typeset -A mx_setprog
+mx_setprog["32"]="$mx_dir/xregs_set.32"
+mx_setprog["64"]="$mx_dir/xregs_set.64"
+
+warn()
+{
+	typeset msg="$*"
+	echo "TEST FAILED: $msg" >&2
+	mx_exit=1
+}
+
+fatal()
+{
+	typeset msg="$*"
+	[[ -z "$msg" ]] && msg="failed"
+	echo "$mx_arg0: $msg" >&2
+	exit 1
+}
+
+cleanup()
+{
+	[[ -n "$mx_tmpdir" ]] && rm -rf "$mx_tmpdir"
+}
+
+setup()
+{
+	if ! mkdir $mx_tmpdir; then
+		fatal "failed to create temporary directory: $mx_tmpdir"
+	fi
+}
+
+check_file()
+{
+	typeset src="$1"
+	typeset act="$2"
+	typeset desc="$3"
+
+	if [[ ! -f "$act" ]]; then
+		warn "failed to generate $act"
+		return
+	fi
+
+	if ! diff -q $src $act; then
+		diff -u $src $act
+		warn "$desc: $act did not match expected output"
+	else
+		printf "TEST PASSED: %s\n" "$desc"
+	fi
+}
+
+#
+# Run one instance of mdb to grab and get our first pass data files
+# against a real process. We're not really counting on our exit status
+# from mdb here, mostly on whether or not the generated files all exist
+# in the end. We gather data from the generated cores in a separate run.
+#
+run_live_mdb()
+{
+	typeset prog="$1"
+	typeset seed="$2"
+	typeset fpregs="$3"
+	typeset core="$4"
+	typeset coreloc="$5"
+
+	mdb $prog <<EOF
+yield::bp
+::run $seed
+::tmodel lwp
+::fpregs ! cat > $fpregs.lwp
+::tmodel thread
+::fpregs ! cat > $fpregs.thread
+_uberdata::printf "$core.%u" uberdata_t pid ! cat > $coreloc
+::gcore -o $core
+::kill
+\$q
+EOF
+}
+
+#
+# We've been given a core file that matches something we just ran above. We
+# should be able to read the ::fpregs from it and get the exact same data.
+#
+check_core()
+{
+	typeset core="$1"
+	typeset output="$2"
+	typeset check="$3"
+
+	if ! mdb -e "::fpregs ! cat > $output" $core; then
+		warn "mdb failed to get ::fpregs from $core"
+		return
+	fi
+
+	check_file $check $output "extracted core matches"
+}
+
+run_one_isa()
+{
+	typeset isa="$1"
+	typeset target=${mx_setprog[$isa]}
+	typeset hwprog=${mx_hwtype[$isa]}
+	typeset seed=${mx_seed[$isa]}
+	typeset coreloc="$mx_tmpdir/coreloc"
+	typeset fpu_type=
+	typeset corename=
+	typeset fpregs=
+	typeset check=
+
+	if ! fpu_type=$($hwprog 2>/dev/null); then
+		warn "failed to determine $isa-bit FPU type"
+		return
+	fi
+
+	printf "Discovered FPU: %s %s-bit\n" $fpu_type $isa
+	corename="$mx_tmpdir/core.$fpu_type.$isa"
+	fpregs="$mx_tmpdir/fpregs.$fpu_type.$isa"
+	check="$mx_data/mdb_xregs.$fpu_type.$isa"
+
+	run_live_mdb $target $seed $fpregs $corename $coreloc
+	check_file "$check" "$fpregs.lwp" "$isa-bit $fpu_type ::fpregs (lwp)"
+	check_file "$check" "$fpregs.lwp" "$isa-bit $fpu_type ::fpregs (thread)"
+
+	if [[ ! -f "$coreloc" ]]; then
+		warn "missing core file location file, cannot run core tests"
+		return
+	fi
+
+	typeset -i ncores=0
+	for f in $(cat $coreloc); do
+		((ncores++))
+		if [[ ! -f "$f" ]]; then
+			warn "core file location $f is not a file"
+			continue
+		fi
+
+		check_core "$f" "$mx_tmpdir/fpregs.core" $check
+	done
+
+	if ((ncores == 0)); then
+		warn "No core files found!"
+	fi
+}
+
+setup
+trap 'cleanup' EXIT
+
+run_one_isa "32"
+run_one_isa "64"
+
+exit $mx_exit
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/proc_writes.ksh	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,142 @@
+#!/usr/bin/ksh
+#
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2023 Oxide Computer Company
+#
+
+#
+# This shell script is a runner that tries to verify that if we write to
+# a target program's xregs via /proc, that it actualy sees the same
+# register contents that we put in. There are four different pieces to
+# this:
+#
+#  o The 'proc_xregs_set' binaries which sets the registers of the target.
+#    Right now we expect the bitness to match whatever it is running against.
+#  o The 'xregs_dump' binaries which dump the FPU state that they have in a
+#    bespoke format.
+#  o The output data files that we use to try to match what is generated.
+#  o The 'xsu_hwtype' binaries which tell us what data format we should expect.
+#
+# We have to discover what kind of hardware our target is running on and then
+# that allows us to ensure that we can match up the right input and output
+# files. We always run both the 32-bit and 64-bit versions of this; however,
+# because 32-bit programs have access to fewer registers, they have a different
+# data file to match against.
+#
+# We also reprat the process of 'proc_xregs_set' using the fpregs binary
+# instead. That allows us to also verify some of the same behavior, but also
+# pieces of fpregs functionality.
+#
+
+unalias -a
+set -o pipefail
+export LANG=C.UTF-8
+
+pw_exit=0;
+pw_arg0="$(basename $0)"
+pw_dir="$(dirname $0)"
+pw_datadir="$pw_dir/data"
+
+#
+# The starting value is used to determine the way that the regiser contents are
+# set in the target's FPU. If you change this value, the check files must be
+# updated.
+#
+typeset -A pw_start
+pw_start["xregs32"]="0xc120ff06"
+pw_start["xregs64"]="0xfaceecaf"
+pw_start["fpregs32"]="0x20190909"
+pw_start["fpregs64"]="0x28186002"
+
+typeset -A pw_hwtypes
+typeset -A pw_dump
+typeset -A pw_prog
+pw_hwtypes["32"]="$pw_dir/xsu_hwtype.32"
+pw_hwtypes["64"]="$pw_dir/xsu_hwtype.64"
+pw_dump["32"]="$pw_dir/xregs_dump.32"
+pw_dump["64"]="$pw_dir/xregs_dump.64"
+pw_prog["xregs32"]="$pw_dir/proc_xregs_set.32"
+pw_prog["xregs64"]="$pw_dir/proc_xregs_set.64"
+pw_prog["fpregs32"]="$pw_dir/fpregs.32"
+pw_prog["fpregs64"]="$pw_dir/fpregs.64"
+
+#
+# All our victim programs are usually the same here and have a single breakpoint
+# we want. This should become an associative array if this changes in the
+# future.
+#
+pw_bkpt="xsu_getfpu"
+
+warn()
+{
+	typeset msg="$*"
+	echo "TEST FAILED: $msg" >&2
+	pw_exit=1
+}
+
+run_single()
+{
+	typeset prog="$1"
+	typeset arch="$2"
+	typeset comb="$prog$arch"
+	typeset fpu_type=
+	typeset start=${pw_start[$comb]}
+	typeset hwtype=${pw_hwtypes[$arch]}
+	typeset dump=${pw_dump[$arch]}
+	typeset exe=${pw_prog[$comb]}
+	typeset output=
+	typeset check=
+
+	if ! fpu_type=$($hwtype 2>/dev/null); then
+		warn "failed to determine $arch-bit FPU type"
+		return
+	fi
+
+	printf "Discovered FPU: %s %s-bit\n" $fpu_type $arch
+	output="/tmp/$prog.$fpu_type.$arch.$$"
+	check="$pw_datadir/proc_writes.$prog.$fpu_type.$arch"
+
+	if ! [[ -r $check ]]; then
+		warn "missing expected output file $check"
+		return;
+	fi
+
+	printf "Running %s %s %s %s\n" $exe $dump $output $start
+	if ! $exe $dump $output $start $pw_bkpt; then
+		warn "$exe did not execute successfully"
+		rm -f $output
+	fi
+
+	if ! diff -q $check $output; then
+		diff -u $check $output
+		warn "$fpu_type $arch-bit FPU regs did not match expected " \
+		    "output"
+	else
+		printf "TEST PASSED: %s %s-bit FPU regs matched /proc write\n" \
+		    $fpu_type $arch
+	fi
+
+	rm -f $output
+}
+
+run_single xregs 32
+run_single xregs 64
+run_single fpregs 32
+run_single fpregs 64
+
+if (( pw_exit == 0 )); then
+	printf "All tests passed successfully\n"
+fi
+
+exit $pw_exit
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/proc_xregs_set.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,103 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This program pairs with the xregs_dump.32 and xregs_dump.64 program. It's
+ * main purpose is to use /proc to overwrite the FPU contents right before our
+ * target program calls xsu_getfpu().
+ *
+ * To accomplish this we end up using libproc for some mischief via support
+ * routines. In particular we go through the following to logically accomplish
+ * this:
+ *
+ *   o Generate the target FPU contents that we'll write (seeded from the CLI)
+ *   o Explicitly create the process, which will be stopped.
+ *   o Set it to be killed if we die.
+ *   o Find the xsu_getfpu() symbol in the target and set a breakpoint.
+ *   o Resume execution of the process.
+ *   o When the break point hits, use libproc to set the FPU.
+ *   o Delete the breakpoint and resume the process, which will print the FPU
+ *     regs to a designated file.
+ *   o Verify the process successfully terminates and returns 0.
+ *
+ * A critical assumption here is that our hardware support is not going to
+ * change between processes (something that may be mucked around with via
+ * environment variables for rtld).
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <string.h>
+
+#include "xsave_util.h"
+
+static xsu_fpu_t fpu;
+
+int
+main(int argc, char *argv[])
+{
+	uint32_t seed, hwsup;
+	unsigned long ul;
+	char *eptr;
+	prxregset_t *prx;
+	size_t prx_len;
+	xsu_proc_t xp;
+
+	if (argc != 5) {
+		errx(EXIT_FAILURE, "missing args: <prog> <output file> "
+		    "<seed> <func>");
+	}
+
+	errno = 0;
+	ul = strtoul(argv[3], &eptr, 0);
+	if (errno != 0 || *eptr != '\0') {
+		errx(EXIT_FAILURE, "seed value is bad: %s", argv[3]);
+	}
+
+#if defined(_LP64)
+	if (ul > UINT32_MAX) {
+		errx(EXIT_FAILURE, "seed %s, exceeds [0, UINT32_MAX]", argv[3]);
+	}
+#endif
+
+	seed = (uint32_t)ul;
+	hwsup = xsu_hwsupport();
+	xsu_fill(&fpu, hwsup, seed);
+	xsu_fpu_to_xregs(&fpu, hwsup, &prx, &prx_len);
+
+	(void) memset(&xp, 0, sizeof (xsu_proc_t));
+	xp.xp_prog = argv[1];
+	xp.xp_arg = argv[2];
+	xp.xp_object = "a.out";
+	xp.xp_symname = argv[4];
+
+	xsu_proc_bkpt(&xp);
+	/* We know that libc always creates a default thread with id of 1 */
+	if (Plwp_setxregs(xp.xp_proc, 1, prx, prx_len) != 0) {
+		err(EXIT_FAILURE, "failed to set target's xregs");
+	}
+
+	xsu_proc_finish(&xp);
+
+	if (WEXITSTATUS(xp.xp_wait) != EXIT_SUCCESS) {
+		errx(EXIT_FAILURE, "our target process didn't exit non-zero, "
+		    "got %d", WEXITSTATUS(xp.xp_wait));
+	}
+
+	return (EXIT_SUCCESS);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/setcontext_replace.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,66 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * The purpose of this program is to go through and replace all the FPU
+ * registers in the floating point state in a ucontext_t and verify that we see
+ * what we expected here.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <ucontext.h>
+
+#include "xsave_util.h"
+
+xsu_fpu_t to_set, found;
+
+static void
+setcontext_replace_check(uint32_t hwsup)
+{
+	xsu_getfpu(&found, hwsup);
+	if (xsu_same(&to_set, &found, hwsup)) {
+		(void) printf("TEST PASSED: setcontext() correctly wrote FPU "
+		    "contents\n");
+		exit(EXIT_SUCCESS);
+	} else {
+		errx(EXIT_FAILURE, "TEST_FAILED: setcontext() did not write "
+		    "full FPU state");
+	}
+}
+
+int
+main(void)
+{
+	ucontext_t *ctx;
+	uint32_t start = arc4random();
+	uint32_t hwsup = xsu_hwsupport();
+
+	ctx = ucontext_alloc(0);
+	if (ctx == NULL) {
+		err(EXIT_FAILURE, "failed to get allocate ucontext_t");
+	}
+	(void) printf("filling starting at 0x%x\n", start);
+	xsu_fill(&to_set, hwsup, start);
+	if (getcontext_extd(ctx, 0) != 0) {
+		err(EXIT_FAILURE, "failed to get extended context");
+	}
+
+	xsu_overwrite_uctx(ctx, &to_set, hwsup);
+	makecontext(ctx, setcontext_replace_check, 1, hwsup);
+	(void) setcontext(ctx);
+
+	err(EXIT_FAILURE, "TEST FAILED: set context did not work!");
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/setcontext_restore.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,75 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This test goes through and verifies that the FPU contents are properly
+ * restored with a setcontext call.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <limits.h>
+
+#include "xsave_util.h"
+
+xsu_fpu_t init_set, override, found;
+volatile int exit_status = EXIT_SUCCESS;
+
+static void
+setcontext_restore_check(uint32_t hwsup)
+{
+	xsu_getfpu(&found, hwsup);
+	if (xsu_same(&init_set, &found, hwsup)) {
+		(void) printf("TEST PASSED: setcontext() correctly restored "
+		    "clobbered FPU contents\n");
+		exit(exit_status);
+	} else {
+		errx(EXIT_FAILURE, "TEST_FAILED: setcontext() did not properly "
+		    "restore clobbered FPU contents");
+	}
+}
+
+int
+main(void)
+{
+	ucontext_t *ctx;
+	uint32_t start = arc4random();
+	uint32_t hwsup = xsu_hwsupport();
+
+	ctx = ucontext_alloc(0);
+	if (ctx == NULL) {
+		err(EXIT_FAILURE, "failed to get allocate ucontext_t");
+	}
+	(void) printf("filling starting at 0x%x\n", start);
+	xsu_fill(&init_set, hwsup, start);
+	xsu_fill(&override, hwsup, start + INT_MAX);
+	xsu_setfpu(&init_set, hwsup);
+
+	if (getcontext_extd(ctx, 0) != 0) {
+		err(EXIT_FAILURE, "failed to get extended context");
+	}
+
+	makecontext(ctx, setcontext_restore_check, 1, hwsup);
+	xsu_setfpu(&override, hwsup);
+	xsu_getfpu(&found, hwsup);
+	if (!xsu_same(&override, &found, hwsup)) {
+		warnx("TEST FAILED: override FPU data not found!");
+		exit_status = EXIT_FAILURE;
+	}
+	(void) setcontext(ctx);
+
+	err(EXIT_FAILURE, "TEST FAILED: set context did not work!");
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/signal_restore.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,119 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Comptuer Company
+ */
+
+/*
+ * Verify that the FPU contents are correctly restored after taking a signal. We
+ * do this by going through and setting up a signal handler for SIGINFO and then
+ * we do the following as tightly as possible: overwriting the FPU contents and
+ * then calling thr_kill(). As part of the regression for #15254, we also
+ * purposefully go off CPU in the signal handler to try to wreak havoc.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <limits.h>
+#include <signal.h>
+#include <thread.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "xsave_util.h"
+
+static xsu_fpu_t init_vals, signal_vals, found;
+static volatile int exit_status = EXIT_SUCCESS;
+static volatile int took_sig = 0;
+static uint32_t sr_hwsup;
+
+static void
+signal_restore_siginfo(int sig, siginfo_t *sip, void *ucp)
+{
+	struct timespec ts;
+	took_sig = 1;
+
+	ts.tv_sec = 0;
+	ts.tv_nsec = 10 * MILLISEC;
+
+	/*
+	 * yield doesn't guarantee that we go off CPU, but try a few anyways.
+	 * There's a slight chance that nanosleep will modify the FPU state, but
+	 * we can hope we're lucky and that the libc function won'.
+	 */
+	xsu_setfpu(&signal_vals, sr_hwsup);
+	yield();
+	yield();
+	(void) nanosleep(&ts, NULL);
+	xsu_getfpu(&found, sr_hwsup);
+
+	if (xsu_same(&signal_vals, &found, sr_hwsup)) {
+		(void) printf("TEST PASSED: FPU contents didn't change in "
+		    "signal handler\n");
+	} else {
+		warnx("TEST FAILED: FPU contents changed in signal handler!");
+		exit_status = EXIT_FAILURE;
+	}
+
+}
+
+int
+main(void)
+{
+	int ret;
+	thread_t self = thr_self();
+	uint32_t start = arc4random();
+	uint32_t hwsup = xsu_hwsupport();
+	struct sigaction sa;
+
+	sr_hwsup = hwsup;
+	sa.sa_sigaction = signal_restore_siginfo;
+	sa.sa_flags = SA_RESETHAND;
+
+	if (sigaction(SIGINFO, &sa, NULL) != 0) {
+		errx(EXIT_FAILURE, "TEST FAILED: failed to set up signal "
+		    "handler");
+	}
+
+	(void) printf("filling starting at 0x%x\n", start);
+	xsu_fill(&init_vals, hwsup, start);
+	xsu_fill(&signal_vals, hwsup, start + INT_MAX);
+
+	(void) memset(&sa, 0, sizeof (struct sigaction));
+
+	xsu_setfpu(&init_vals, hwsup);
+	ret = thr_kill(self, SIGINFO);
+	xsu_getfpu(&found, hwsup);
+
+	if (ret != 0) {
+		errc(EXIT_FAILURE, ret, "TEST FAILED: failed to deliver "
+		    "signal");
+	}
+
+	if (took_sig == 0) {
+		errx(EXIT_FAILURE, "TEST FAILED: signal handler did not run");
+	}
+
+	(void) printf("TEST PASSED: SIGINFO successfully delivered\n");
+
+	if (xsu_same(&init_vals, &found, hwsup)) {
+		(void) printf("TEST PASSED: FPU contents successfully "
+		    "restored\n");
+	} else {
+		warnx("TEST FAILED: FPU contents were not restored!");
+		exit_status = EXIT_FAILURE;
+	}
+
+	return (exit_status);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/swapcontext_extd.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,111 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This test exercises the swapcontext_extd(2) functionality and verifies that
+ * we can swap contexts tht have both extended and non-extended ucontext_t's and
+ * that we can get at that state in both cases. This test relies on extended
+ * states as we try to validate and ensure that they exist.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "xsave_util.h"
+
+static xsu_fpu_t mk_fpu, orig_fpu, found;
+static ucontext_t *mk_ctx, *orig_ctx;
+static volatile int estatus = EXIT_SUCCESS;
+
+static void
+mkctx_target(uint32_t hwsup)
+{
+	xsu_getfpu(&found, hwsup);
+	if (!xsu_same(&mk_fpu, &found, hwsup)) {
+		estatus = EXIT_FAILURE;
+		warnx("TEST FAILED: initial swap had bad FPU");
+	} else {
+		(void) printf("TEST PASSED: initial swap had correct FPU\n");
+	}
+
+	(void) swapcontext_extd(mk_ctx, 0, orig_ctx);
+	err(EXIT_FAILURE, "swapcontext_extd() back failed");
+}
+
+static void
+mkctx_failure(void)
+{
+	errx(EXIT_FAILURE, "swapcontext_extd() called failure func");
+}
+
+int
+main(void)
+{
+	uint32_t hwsup = xsu_hwsupport();
+	uint32_t start = arc4random();
+
+	xsu_fill(&mk_fpu, hwsup, start);
+	xsu_fill(&orig_fpu, hwsup, start + INT_MAX);
+
+	mk_ctx = ucontext_alloc(0);
+	if (mk_ctx == NULL)
+		err(EXIT_FAILURE, "failed to allocate extended ucontext_t");
+	orig_ctx = ucontext_alloc(0);
+	if (orig_ctx == NULL)
+		err(EXIT_FAILURE, "failed to allocate extended ucontext_t");
+
+	/*
+	 * Set the FPU and snag our initial context. We'll use makecontext to
+	 * call this and then change our FPU, swap and start checking and then
+	 * swap back.
+	 */
+	xsu_setfpu(&mk_fpu, hwsup);
+	if (getcontext_extd(mk_ctx, 0) != 0) {
+		errx(EXIT_FAILURE, "failed to get initial extended context for "
+		    "makecontext");
+	}
+
+	makecontext(mk_ctx, mkctx_target, 1, hwsup);
+	xsu_setfpu(&orig_fpu, hwsup);
+	if (swapcontext_extd(orig_ctx, 0, mk_ctx) != 0) {
+		err(EXIT_FAILURE, "failed to swap contexts");
+	}
+	xsu_getfpu(&found, hwsup);
+	if (!xsu_same(&orig_fpu, &found, hwsup)) {
+		estatus = EXIT_FAILURE;
+		warnx("TEST FAILED: swap back did not have the right FPU");
+	} else {
+		(void) printf("TEST PASSED: swap back had the correct FPU\n");
+	}
+
+	makecontext(mk_ctx, mkctx_failure, 0);
+	if (swapcontext_extd(orig_ctx, 23, mk_ctx) != -1) {
+		errx(EXIT_FAILURE, "somehow got back from error test "
+		    "swapcontext_extd() with bad func");
+	}
+	if (errno != EINVAL) {
+		estatus = EXIT_FAILURE;
+		warnx("TEST FAILED: swapcontext_extd() with bad flags had "
+		    "errno %d expected EINVAL", errno);
+	} else {
+		(void) printf("TEST PASSED: swapcontext_extd() with bad flags "
+		    "has correct errno (EINVAL)\n");
+	}
+
+	return (estatus);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xregs_dump.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,50 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This is a simple program that is meant for use as other programs consuming
+ * it. It simply reads the extended registers and dumps them. The expectation
+ * is someone else is showing up and doing something ahead of that.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "xsave_util.h"
+
+static xsu_fpu_t fpu;
+
+int
+main(int argc, char *argv[])
+{
+	FILE *f;
+	uint32_t hwsup;
+
+	if (argc != 2) {
+		errx(EXIT_FAILURE, "missing required filename to write to");
+	}
+
+	hwsup = xsu_hwsupport();
+	f = fopen(argv[1], "w+");
+	if (f == NULL) {
+		err(EXIT_FAILURE, "failed to open %s", argv[1]);
+	}
+
+	xsu_getfpu(&fpu, hwsup);
+	xsu_dump(f, &fpu, hwsup);
+
+	return (EXIT_SUCCESS);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xregs_roundtrip.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,136 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * Verify that we can read the xregs of a thread and write them back intact.
+ * This uses libproc as a wrapper. We then start the thread running again and
+ * attempt to write to /proc ourselves to expect an EBUSY because the thread is
+ * not stopped.
+ */
+
+#include <libproc.h>
+#include <thread.h>
+#include <stdlib.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/sysmacros.h>
+
+#include "xsave_util.h"
+
+int
+main(void)
+{
+	int ret, fd;
+	ssize_t sret;
+	struct ps_prochandle *P;
+	struct ps_lwphandle *L;
+	thread_t targ;
+	prxregset_t *prx, *prx_alt;
+	size_t prx_len, prx_alt_len;
+	struct iovec iov[2];
+	long cmd = PCSXREG;
+
+	P = Pgrab(getpid(), PGRAB_RDONLY, &ret);
+	if (P == NULL) {
+		errx(EXIT_FAILURE, "failed to grab ourself: %s",
+		    Pgrab_error(ret));
+	}
+
+	ret = thr_create(NULL, 0, xsu_sleeper_thread, NULL, THR_DETACHED,
+	    &targ);
+	if (ret != 0) {
+		errc(EXIT_FAILURE, ret, "failed to create sleeper thread");
+	}
+
+	L = Lgrab(P, targ, &ret);
+	if (L == NULL) {
+		errx(EXIT_FAILURE, "failed to grab our sleeper thread: %s",
+		    Lgrab_error(ret));
+	}
+
+	ret = Lstop(L, 0);
+	if (ret != 0) {
+		err(EXIT_FAILURE, "failed to stop the sleeper thread");
+	}
+
+	if (Lgetxregs(L, &prx, &prx_len) != 0) {
+		err(EXIT_FAILURE, "failed to get xregs");
+	}
+
+	(void) printf("TEST PASSED: sucessfully got initial xregs\n");
+
+	if (Lsetxregs(L, prx, prx_len) != 0) {
+		err(EXIT_FAILURE, "failed to set xregs");
+	}
+
+	(void) printf("TEST PASSED: sucessfully set xregs\n");
+
+	if (Lgetxregs(L, &prx_alt, &prx_alt_len) != 0) {
+		err(EXIT_FAILURE, "failed to get xregs after write");
+	}
+
+	if (prx_len != prx_alt_len) {
+		errx(EXIT_FAILURE, "xreg length changed across a write: "
+		    "originally found %zu, now %zu", prx_len, prx_alt_len);
+	}
+
+	if (memcmp(prx, prx_alt, prx_len) != 0) {
+		const uint8_t *a = (uint8_t *)prx;
+		const uint8_t *b = (uint8_t *)prx_alt;
+		for (size_t i = 0; i < prx_len; i++) {
+			if (a[i] != b[i]) {
+				(void) fprintf(stderr, "prx[0x%x] = 0x%02x, "
+				    "prx_alt[0x%x] = 0x%02x\n", i, a[i], i,
+				    b[i]);
+			}
+		}
+		errx(EXIT_FAILURE, "xregs were not the same!");
+	}
+
+	(void) printf("TEST PASSED: round-trip xregs\n");
+
+	if (Lsetrun(L, 0, 0) != 0) {
+		err(EXIT_FAILURE, "failed to start sleeper thread");
+	}
+
+	/*
+	 * We write to /proc directly ourselves as a way to avoid libproc's own
+	 * checks for the state of the thread.
+	 */
+	fd = Lctlfd(L);
+	if (fd < 0) {
+		errx(EXIT_FAILURE, "failed to get sleeper thread control d");
+	}
+
+	iov[0].iov_base = (char *)&cmd;
+	iov[0].iov_len = sizeof (long);
+	iov[1].iov_base = (char *)prx;
+	iov[1].iov_len = prx_len;
+	sret = writev(fd, iov, ARRAY_SIZE(iov));
+	if (sret != -1) {
+		errx(EXIT_FAILURE, "writev returned %zd, but expected -1",
+		    sret);
+	}
+
+	if (errno != EBUSY) {
+
+	}
+
+	(void) printf("TEST PASSED: got EBUSY with PCSXREG to running "
+	    "thread\n");
+	Plwp_freexregs(P, prx, prx_len);
+	return (EXIT_SUCCESS);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xregs_set.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,58 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This is a program that is meant to be used as a target for libproc and mdb.
+ * It uses a CLI-based seed and sets its FPU to a set of known values. After
+ * that, we call a function that can be used as a no-op target to dump the FPU
+ * state, attempting to minimize the number of instructions inbetween
+ * operations. We use the 'yield(2)' system call and library function in libc
+ * for that as it's something that'll not really do much and it is very unlikely
+ * the compiler will use the FPU between function calls.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "xsave_util.h"
+
+static xsu_fpu_t fpu;
+
+int
+main(int argc, char *argv[])
+{
+	uint32_t hwsup;
+	char *eptr;
+	unsigned long ul;
+
+	if (argc != 2) {
+		errx(EXIT_FAILURE, "missing required seed");
+	}
+
+	errno = 0;
+	ul = strtoul(argv[1], &eptr, 0);
+	if (errno != 0 || *eptr != '\0') {
+		errx(EXIT_FAILURE, "seed value is bad: %s", argv[3]);
+	}
+
+	hwsup = xsu_hwsupport();
+	xsu_fill(&fpu, hwsup, (uint32_t)ul);
+	xsu_setfpu(&fpu, hwsup);
+
+	yield();
+	return (EXIT_SUCCESS);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsave_asm32.S	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,169 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+	.file	"xsave_asm32.s"
+
+/*
+ * Test utility routines that we need assembler for. 32-bit addition.
+ */
+
+#include <sys/asm_linkage.h>
+#include "xsave_util.h"
+
+#define	GET_FPU_XMM(src, i)	\
+	leal	(i * XSU_ZMM_U32 * 4)(src), %eax; \
+	movdqu	%xmm##i, (%eax)
+
+#define	GET_FPU_YMM(src, i)	\
+	leal	(i * XSU_ZMM_U32 * 4)(src), %eax; \
+	vmovdqu	%ymm##i, (%eax)
+
+#define	GET_FPU_ZMM(src, i)	\
+	leal	(i * XSU_ZMM_U32 * 4)(src), %eax; \
+	vmovdqu64	%zmm##i, (%eax)
+
+#define	GET_MASK(src, i)	\
+	leal	(0x800 + i * 8)(src), %eax; \
+	kmovq	%k##i, (%eax)
+
+	/*
+	 * void xsu_getfpu(xsu_fpu_t *data, uint32_t type)
+	 *
+	 * Our job is to get the entire contents of the FPU and save it into our
+	 * data structure.
+	 */
+	ENTRY(xsu_getfpu)
+	movl	8(%esp), %eax
+	movl	4(%esp), %edx
+	cmpl	$XSU_XMM, %eax
+	je	get_xmm
+	cmpl	$XSU_YMM, %eax
+	je	get_ymm
+	cmpl	$XSU_ZMM, %eax
+	je	get_zmm
+	call	abort
+get_xmm:
+	GET_FPU_XMM(%edx, 0)
+	GET_FPU_XMM(%edx, 1)
+	GET_FPU_XMM(%edx, 2)
+	GET_FPU_XMM(%edx, 3)
+	GET_FPU_XMM(%edx, 4)
+	GET_FPU_XMM(%edx, 5)
+	GET_FPU_XMM(%edx, 6)
+	GET_FPU_XMM(%edx, 7)
+	jmp	get_done
+get_ymm:
+	GET_FPU_YMM(%edx, 0)
+	GET_FPU_YMM(%edx, 1)
+	GET_FPU_YMM(%edx, 2)
+	GET_FPU_YMM(%edx, 3)
+	GET_FPU_YMM(%edx, 4)
+	GET_FPU_YMM(%edx, 5)
+	GET_FPU_YMM(%edx, 6)
+	GET_FPU_YMM(%edx, 7)
+	jmp	get_done
+get_zmm:
+	GET_FPU_ZMM(%edx, 0)
+	GET_FPU_ZMM(%edx, 1)
+	GET_FPU_ZMM(%edx, 2)
+	GET_FPU_ZMM(%edx, 3)
+	GET_FPU_ZMM(%edx, 4)
+	GET_FPU_ZMM(%edx, 5)
+	GET_FPU_ZMM(%edx, 6)
+	GET_FPU_ZMM(%edx, 7)
+	GET_MASK(%edx, 0)
+	GET_MASK(%edx, 1)
+	GET_MASK(%edx, 2)
+	GET_MASK(%edx, 3)
+	GET_MASK(%edx, 4)
+	GET_MASK(%edx, 5)
+	GET_MASK(%edx, 6)
+	GET_MASK(%edx, 7)
+get_done:
+	ret
+	SET_SIZE(xsu_getfpu)
+
+#define	SET_FPU_XMM(src, i)	\
+	leal	(i * XSU_ZMM_U32 * 4)(src), %eax; \
+	movdqu	(%eax), %xmm##i
+
+#define	SET_FPU_YMM(src, i)	\
+	leal	(i * XSU_ZMM_U32 * 4)(src), %eax; \
+	vmovdqu	(%eax), %ymm##i
+
+#define	SET_FPU_ZMM(src, i)	\
+	leal	(i * XSU_ZMM_U32 * 4)(src), %eax; \
+	vmovdqu64	(%eax), %zmm##i
+
+#define	SET_MASK(src, i)	\
+	leal	(0x800 + i * 8)(src), %eax; \
+	kmovq	(%eax), %k##i
+
+	/*
+	 * void xsu_setfpu(const xsu_fpu_t *data, uint32_t type)
+	 *
+	 * Our job is to override the contents of the FPU with this structure
+	 * that we've been given. The type indicates how much of it to use.
+	 */
+	ENTRY(xsu_setfpu)
+	movl	8(%esp), %eax
+	movl	4(%esp), %edx
+	cmpl	$XSU_XMM, %eax
+	je	set_xmm
+	cmpl	$XSU_YMM, %eax
+	je	set_ymm
+	cmpl	$XSU_ZMM, %eax
+	je	set_zmm
+	call	abort
+set_xmm:
+	SET_FPU_XMM(%edx, 0)
+	SET_FPU_XMM(%edx, 1)
+	SET_FPU_XMM(%edx, 2)
+	SET_FPU_XMM(%edx, 3)
+	SET_FPU_XMM(%edx, 4)
+	SET_FPU_XMM(%edx, 5)
+	SET_FPU_XMM(%edx, 6)
+	SET_FPU_XMM(%edx, 7)
+	jmp	set_done
+set_ymm:
+	SET_FPU_YMM(%edx, 0)
+	SET_FPU_YMM(%edx, 1)
+	SET_FPU_YMM(%edx, 2)
+	SET_FPU_YMM(%edx, 3)
+	SET_FPU_YMM(%edx, 4)
+	SET_FPU_YMM(%edx, 5)
+	SET_FPU_YMM(%edx, 6)
+	SET_FPU_YMM(%edx, 7)
+	jmp	set_done
+set_zmm:
+	SET_FPU_ZMM(%edx, 0)
+	SET_FPU_ZMM(%edx, 1)
+	SET_FPU_ZMM(%edx, 2)
+	SET_FPU_ZMM(%edx, 3)
+	SET_FPU_ZMM(%edx, 4)
+	SET_FPU_ZMM(%edx, 5)
+	SET_FPU_ZMM(%edx, 6)
+	SET_FPU_ZMM(%edx, 7)
+	SET_MASK(%edx, 0)
+	SET_MASK(%edx, 1)
+	SET_MASK(%edx, 2)
+	SET_MASK(%edx, 3)
+	SET_MASK(%edx, 4)
+	SET_MASK(%edx, 5)
+	SET_MASK(%edx, 6)
+	SET_MASK(%edx, 7)
+set_done:
+	ret
+	SET_SIZE(xsu_setfpu)
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsave_asm64.S	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,247 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+	.file	"xsave_asm64.s"
+
+/*
+ * Test utility routines that we need assembler for. 64-bit addition.
+ */
+
+#include <sys/asm_linkage.h>
+#include "xsave_util.h"
+
+#define	GET_FPU_XMM(src, i)	\
+	leaq	(i * XSU_ZMM_U32 * 4)(src), %rax; \
+	movdqu	%xmm##i, (%rax)
+
+#define	GET_FPU_YMM(src, i)	\
+	leaq	(i * XSU_ZMM_U32 * 4)(src), %rax; \
+	vmovdqu	%ymm##i, (%rax)
+
+#define	GET_FPU_ZMM(src, i)	\
+	leaq	(i * XSU_ZMM_U32 * 4)(src), %rax; \
+	vmovdqu64	%zmm##i, (%rax)
+
+#define	GET_MASK(src, i)	\
+	leaq	(0x800 + i * 8)(src), %rax; \
+	kmovq	%k##i, (%rax)
+
+	/*
+	 * void xsu_getfpu(xsu_fpu_t *data, uint32_t type)
+	 *
+	 * Our job is to get the entire contents of the FPU and save it into our
+	 * data structure.
+	 */
+	ENTRY(xsu_getfpu)
+	cmpl	$XSU_XMM, %esi
+	je	get_xmm
+	cmpl	$XSU_YMM, %esi
+	je	get_ymm
+	cmpl	$XSU_ZMM, %esi
+	je	get_zmm
+	call	abort
+get_xmm:
+	GET_FPU_XMM(%rdi, 0)
+	GET_FPU_XMM(%rdi, 1)
+	GET_FPU_XMM(%rdi, 2)
+	GET_FPU_XMM(%rdi, 3)
+	GET_FPU_XMM(%rdi, 4)
+	GET_FPU_XMM(%rdi, 5)
+	GET_FPU_XMM(%rdi, 6)
+	GET_FPU_XMM(%rdi, 7)
+	GET_FPU_XMM(%rdi, 8)
+	GET_FPU_XMM(%rdi, 9)
+	GET_FPU_XMM(%rdi, 10)
+	GET_FPU_XMM(%rdi, 11)
+	GET_FPU_XMM(%rdi, 12)
+	GET_FPU_XMM(%rdi, 13)
+	GET_FPU_XMM(%rdi, 14)
+	GET_FPU_XMM(%rdi, 15)
+	jmp	get_done
+get_ymm:
+	GET_FPU_YMM(%rdi, 0)
+	GET_FPU_YMM(%rdi, 1)
+	GET_FPU_YMM(%rdi, 2)
+	GET_FPU_YMM(%rdi, 3)
+	GET_FPU_YMM(%rdi, 4)
+	GET_FPU_YMM(%rdi, 5)
+	GET_FPU_YMM(%rdi, 6)
+	GET_FPU_YMM(%rdi, 7)
+	GET_FPU_YMM(%rdi, 8)
+	GET_FPU_YMM(%rdi, 9)
+	GET_FPU_YMM(%rdi, 10)
+	GET_FPU_YMM(%rdi, 11)
+	GET_FPU_YMM(%rdi, 12)
+	GET_FPU_YMM(%rdi, 13)
+	GET_FPU_YMM(%rdi, 14)
+	GET_FPU_YMM(%rdi, 15)
+	jmp	get_done
+get_zmm:
+	GET_FPU_ZMM(%rdi, 0)
+	GET_FPU_ZMM(%rdi, 1)
+	GET_FPU_ZMM(%rdi, 2)
+	GET_FPU_ZMM(%rdi, 3)
+	GET_FPU_ZMM(%rdi, 4)
+	GET_FPU_ZMM(%rdi, 5)
+	GET_FPU_ZMM(%rdi, 6)
+	GET_FPU_ZMM(%rdi, 7)
+	GET_FPU_ZMM(%rdi, 8)
+	GET_FPU_ZMM(%rdi, 9)
+	GET_FPU_ZMM(%rdi, 10)
+	GET_FPU_ZMM(%rdi, 11)
+	GET_FPU_ZMM(%rdi, 12)
+	GET_FPU_ZMM(%rdi, 13)
+	GET_FPU_ZMM(%rdi, 14)
+	GET_FPU_ZMM(%rdi, 15)
+	GET_FPU_ZMM(%rdi, 16)
+	GET_FPU_ZMM(%rdi, 17)
+	GET_FPU_ZMM(%rdi, 18)
+	GET_FPU_ZMM(%rdi, 19)
+	GET_FPU_ZMM(%rdi, 20)
+	GET_FPU_ZMM(%rdi, 21)
+	GET_FPU_ZMM(%rdi, 22)
+	GET_FPU_ZMM(%rdi, 23)
+	GET_FPU_ZMM(%rdi, 24)
+	GET_FPU_ZMM(%rdi, 25)
+	GET_FPU_ZMM(%rdi, 25)
+	GET_FPU_ZMM(%rdi, 26)
+	GET_FPU_ZMM(%rdi, 27)
+	GET_FPU_ZMM(%rdi, 28)
+	GET_FPU_ZMM(%rdi, 29)
+	GET_FPU_ZMM(%rdi, 30)
+	GET_FPU_ZMM(%rdi, 31)
+	GET_MASK(%rdi, 0)
+	GET_MASK(%rdi, 1)
+	GET_MASK(%rdi, 2)
+	GET_MASK(%rdi, 3)
+	GET_MASK(%rdi, 4)
+	GET_MASK(%rdi, 5)
+	GET_MASK(%rdi, 6)
+	GET_MASK(%rdi, 7)
+get_done:
+	ret
+	SET_SIZE(xsu_getfpu)
+
+#define	SET_FPU_XMM(src, i)	\
+	leaq	(i * XSU_ZMM_U32 * 4)(src), %rax; \
+	movdqu	(%rax), %xmm##i
+
+#define	SET_FPU_YMM(src, i)	\
+	leaq	(i * XSU_ZMM_U32 * 4)(src), %rax; \
+	vmovdqu	(%rax), %ymm##i
+
+#define	SET_FPU_ZMM(src, i)	\
+	leaq	(i * XSU_ZMM_U32 * 4)(src), %rax; \
+	vmovdqu64	(%rax), %zmm##i
+
+#define	SET_MASK(src, i)	\
+	leaq	(0x800 + i * 8)(src), %rax; \
+	kmovq	(%rax), %k##i
+
+	/*
+	 * void xsu_setfpu(const xsu_fpu_t *data, uint32_t type)
+	 *
+	 * Our job is to override the contents of the FPU with this structure
+	 * that we've been given. The type indicates how much of it to use.
+	 */
+	ENTRY(xsu_setfpu)
+	cmpl	$XSU_XMM, %esi
+	je	set_xmm
+	cmpl	$XSU_YMM, %esi
+	je	set_ymm
+	cmpl	$XSU_ZMM, %esi
+	je	set_zmm
+	call	abort
+set_xmm:
+	SET_FPU_XMM(%rdi, 0)
+	SET_FPU_XMM(%rdi, 1)
+	SET_FPU_XMM(%rdi, 2)
+	SET_FPU_XMM(%rdi, 3)
+	SET_FPU_XMM(%rdi, 4)
+	SET_FPU_XMM(%rdi, 5)
+	SET_FPU_XMM(%rdi, 6)
+	SET_FPU_XMM(%rdi, 7)
+	SET_FPU_XMM(%rdi, 8)
+	SET_FPU_XMM(%rdi, 9)
+	SET_FPU_XMM(%rdi, 10)
+	SET_FPU_XMM(%rdi, 11)
+	SET_FPU_XMM(%rdi, 12)
+	SET_FPU_XMM(%rdi, 13)
+	SET_FPU_XMM(%rdi, 14)
+	SET_FPU_XMM(%rdi, 15)
+	jmp	set_done
+set_ymm:
+	SET_FPU_YMM(%rdi, 0)
+	SET_FPU_YMM(%rdi, 1)
+	SET_FPU_YMM(%rdi, 2)
+	SET_FPU_YMM(%rdi, 3)
+	SET_FPU_YMM(%rdi, 4)
+	SET_FPU_YMM(%rdi, 5)
+	SET_FPU_YMM(%rdi, 6)
+	SET_FPU_YMM(%rdi, 7)
+	SET_FPU_YMM(%rdi, 8)
+	SET_FPU_YMM(%rdi, 9)
+	SET_FPU_YMM(%rdi, 10)
+	SET_FPU_YMM(%rdi, 11)
+	SET_FPU_YMM(%rdi, 12)
+	SET_FPU_YMM(%rdi, 13)
+	SET_FPU_YMM(%rdi, 14)
+	SET_FPU_YMM(%rdi, 15)
+	jmp	set_done
+set_zmm:
+	SET_FPU_ZMM(%rdi, 0)
+	SET_FPU_ZMM(%rdi, 1)
+	SET_FPU_ZMM(%rdi, 2)
+	SET_FPU_ZMM(%rdi, 3)
+	SET_FPU_ZMM(%rdi, 4)
+	SET_FPU_ZMM(%rdi, 5)
+	SET_FPU_ZMM(%rdi, 6)
+	SET_FPU_ZMM(%rdi, 7)
+	SET_FPU_ZMM(%rdi, 8)
+	SET_FPU_ZMM(%rdi, 9)
+	SET_FPU_ZMM(%rdi, 10)
+	SET_FPU_ZMM(%rdi, 11)
+	SET_FPU_ZMM(%rdi, 12)
+	SET_FPU_ZMM(%rdi, 13)
+	SET_FPU_ZMM(%rdi, 14)
+	SET_FPU_ZMM(%rdi, 15)
+	SET_FPU_ZMM(%rdi, 16)
+	SET_FPU_ZMM(%rdi, 17)
+	SET_FPU_ZMM(%rdi, 18)
+	SET_FPU_ZMM(%rdi, 19)
+	SET_FPU_ZMM(%rdi, 20)
+	SET_FPU_ZMM(%rdi, 21)
+	SET_FPU_ZMM(%rdi, 22)
+	SET_FPU_ZMM(%rdi, 23)
+	SET_FPU_ZMM(%rdi, 24)
+	SET_FPU_ZMM(%rdi, 25)
+	SET_FPU_ZMM(%rdi, 25)
+	SET_FPU_ZMM(%rdi, 26)
+	SET_FPU_ZMM(%rdi, 27)
+	SET_FPU_ZMM(%rdi, 28)
+	SET_FPU_ZMM(%rdi, 29)
+	SET_FPU_ZMM(%rdi, 30)
+	SET_FPU_ZMM(%rdi, 31)
+	SET_MASK(%rdi, 0)
+	SET_MASK(%rdi, 1)
+	SET_MASK(%rdi, 2)
+	SET_MASK(%rdi, 3)
+	SET_MASK(%rdi, 4)
+	SET_MASK(%rdi, 5)
+	SET_MASK(%rdi, 6)
+	SET_MASK(%rdi, 7)
+set_done:
+	ret
+	SET_SIZE(xsu_setfpu)
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsave_baducontext.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,251 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * The purpose of this test is to go through and construct ucontext_t xsave
+ * states that we expect to be invalid and therefore cause setcontext(2) to
+ * fail. We only assume that %ymm state is present here with respect to writing
+ * invalid tests. As if this test runs at all, that will be present.
+ *
+ * This is structured a little differently as we expect this program to fail to
+ * execute and that libc will cause us to abort() if we don't correctly return
+ * from setcontext..
+ */
+
+#include <ucontext.h>
+#include <err.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <sys/sysmacros.h>
+#include <sys/debug.h>
+#include <sys/x86_archext.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "xsave_util.h"
+
+static void *xsave_buf;
+static const char *curtest;
+
+static void
+bad_success(void)
+{
+	errx(EXIT_FAILURE, "TEST FAILED: %s setcontext, took us to success",
+	    curtest);
+}
+
+static void
+test_bad_version(ucontext_t *ctx)
+{
+	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
+	xc->ucx_vers = 23;
+}
+
+static void
+test_bad_length_small(ucontext_t *ctx)
+{
+	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
+	xc->ucx_len = 0;
+}
+
+static void
+test_bad_length_large(ucontext_t *ctx)
+{
+	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
+	xc->ucx_len = INT32_MAX;
+}
+
+/*
+ * As this can run on multiple different systems, we explicitly use bit 8 which
+ * is reserved for a supervisor feature and so should never be valid in this
+ * context.
+ */
+static void
+test_bad_vector(ucontext_t *ctx)
+{
+	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
+	xc->ucx_bv |= (1 << 8);
+}
+
+static void
+test_context_too_short(ucontext_t *ctx)
+{
+	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
+
+	bcopy(xc, xsave_buf, xc->ucx_len);
+	ctx->uc_xsave = (long)(uintptr_t)xsave_buf;
+	xc = (uc_xsave_t *)ctx->uc_xsave;
+	xc->ucx_bv |= XFEATURE_AVX;
+	xc->ucx_len = sizeof (uc_xsave_t) + 0x10;
+}
+
+static void
+test_context_badptr0(ucontext_t *ctx)
+{
+	ctx->uc_xsave = 0;
+}
+
+static void
+test_context_badptr1(ucontext_t *ctx)
+{
+	void *addr = mmap(NULL, sysconf(_SC_PAGESIZE), PROT_NONE,
+	    MAP_PRIVATE | MAP_ANON, -1, 0);
+	if (addr == NULL) {
+		err(EXIT_FAILURE, "failed to get unmapped page");
+	}
+
+	ctx->uc_xsave = (long)(uintptr_t)addr;
+}
+
+static void
+test_context_badptr2(ucontext_t *ctx)
+{
+	long pgsz = sysconf(_SC_PAGESIZE);
+	void *addr = mmap(NULL, pgsz * 2, PROT_NONE, MAP_PRIVATE | MAP_ANON,
+	    -1, 0);
+	if (addr == NULL) {
+		errx(EXIT_FAILURE, "failed to get unmapped page");
+	}
+
+	if (mprotect((void *)((uintptr_t)addr + pgsz), pgsz, PROT_NONE) != 0) {
+		err(EXIT_FAILURE, "failed to mprotect second page");
+	}
+
+	ctx->uc_xsave = (uintptr_t)addr;
+	ctx->uc_xsave += pgsz - sizeof (uint64_t);
+}
+
+static ucontext_t *
+setup_context(void)
+{
+	ucontext_t *ctx = ucontext_alloc(0);
+	if (ctx == NULL) {
+		errx(EXIT_FAILURE, "failed to get allocate ucontext_t");
+	}
+
+	if (getcontext_extd(ctx, 0) != 0) {
+		err(EXIT_FAILURE, "failed to get extended context");
+	}
+	makecontext(ctx, bad_success, 0);
+
+	return (ctx);
+}
+
+typedef struct {
+	void (*bct_func)(ucontext_t *);
+	const char *bct_test;
+	int bct_errno;
+} bad_ucontext_test_t;
+
+/*
+ * Do not use single quote characters in tests below, that'll break the shell
+ * wrapper.
+ */
+static const bad_ucontext_test_t tests[] = {
+	{ test_bad_version, "invalid version", EINVAL },
+	{ test_bad_length_small, "invalid length (small)", EINVAL },
+	{ test_bad_length_large, "invalid length (large)", EINVAL },
+	{ test_bad_vector, "invalid xbv", EINVAL },
+	{ test_context_too_short, "length does not cover AVX", EOVERFLOW },
+	{ test_context_badptr0, "invalid uc_xsave pointer (NULL)", EINVAL },
+	{ test_context_badptr1, "invalid uc_xsave pointer (unmapped page)",
+	    EFAULT },
+	{ test_context_badptr2, "partially invalid uc_xsave (hit "
+	    "unmapped page)", EFAULT },
+};
+
+int
+main(int argc, char *argv[])
+{
+	int c;
+	char *eptr;
+	unsigned long l;
+	const char *testno = NULL;
+	boolean_t do_info = B_FALSE, do_run = B_FALSE;
+
+	if (argc < 2) {
+		(void) fprintf(stderr, "Usage:  %s [-c] [-i testno] "
+		    "[-r testno]\n", argv[0]);
+	}
+
+	while ((c = getopt(argc, argv, ":ci:r:")) != -1) {
+		switch (c) {
+		case 'c':
+			(void) printf("%zu\n", ARRAY_SIZE(tests));
+			return (0);
+		case 'i':
+			testno = optarg;
+			do_info = B_TRUE;
+			break;
+		case 'r':
+			testno = optarg;
+			do_run = B_TRUE;
+			break;
+		case ':':
+			errx(EXIT_FAILURE, "Option -%c requires an operand\n",
+			    optopt);
+			break;
+		case '?':
+			errx(EXIT_FAILURE, "Unknown option: -%c\n", optopt);
+			break;
+		}
+	}
+
+	if (testno == NULL) {
+		errx(EXIT_FAILURE, "one of -r and -i must be specified");
+	}
+
+	if (do_run && do_info) {
+		errx(EXIT_FAILURE, "only one of -r and -i may be specified");
+	}
+
+	errno = 0;
+	l = strtoul(testno, &eptr, 0);
+	if (*eptr != 0 || errno != 0) {
+		errx(EXIT_FAILURE, "failed to parse test number: %s", argv[1]);
+	}
+
+	if (l >= ARRAY_SIZE(tests)) {
+		errx(EXIT_FAILURE, "test number %lu is too large\n", l);
+	}
+
+	if (do_info) {
+		/*
+		 * Output info for our wrapper shell script in a way that's not
+		 * too bad to eval.
+		 */
+		(void) printf("errno=%u\ndesc='%s'\n", tests[l].bct_errno,
+		    tests[l].bct_test);
+		return (0);
+	}
+
+	/*
+	 * This is a little gross, but we know right now that the extended
+	 * context is going to be the approximate size that we need for
+	 * operations on the system.
+	 */
+	xsave_buf = ucontext_alloc(0);
+	if (xsave_buf == NULL) {
+		err(EXIT_FAILURE, "failed to alternative xsave buf");
+	}
+
+	ucontext_t *ctx = setup_context();
+	VERIFY3U(ctx->uc_xsave, !=, 0);
+	tests[l].bct_func(ctx);
+	(void) setcontext(ctx);
+	errx(EXIT_FAILURE, "TEST FAILED: setcontext returned despite us "
+	    "expecting a core");
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsave_baducontext.ksh	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,116 @@
+#!/usr/bin/ksh
+#
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2023 Oxide Computer Company
+#
+
+#
+# This test goes through and runs our 32 and 64-bit bad xsave contexts
+# tests using getcontextx() in lieu of the signal handler to create
+# invalid states. setcontext(2) in libc will try to kill the process if
+# this fails in most situations, so we basically go through and detect
+# that this is the case we expect (e.g. the system call returned an
+# error) and go from there.
+#
+
+unalias -a
+set -o pipefail
+
+xsave_arg0="$(basename $0)"
+xsave_dir="$(dirname $0)"
+xsave_bad32="$xsave_dir/xsave_baducontext.32"
+xsave_bad64="$xsave_dir/xsave_baducontext.64"
+xsave_exit=0
+
+warn()
+{
+	typeset msg="$*"
+	echo "TEST FAILED: $msg" >&2
+	xsave_exit=1
+}
+
+run_single()
+{
+	typeset prog=$1
+	typeset caseno=$2
+	typeset info=
+	typeset ret=
+	typeset desc=
+	typeset errno=
+
+	if ! info=$($prog -i $caseno); then
+		warn "failed to get test information for case $caseno"
+		return
+	fi
+
+	if ! eval $info || [[ -z "$desc" ]] || [[ -z "$errno" ]]; then
+		warn "failed to set test information"
+	fi
+
+	dtrace -q -w -c "$prog -r $caseno" -s /dev/stdin <<EOF
+syscall::setcontext:return
+/pid == \$target && arg1 != 0 && errno == $errno/
+{
+	printf("TEST PASSED: $desc\n");
+	stop();
+	raise(SIGKILL);
+	exit(0);
+}
+
+syscall::setcontext:return
+/pid == \$target && arg1 != 0 && errno != $errno/
+{
+	printf("errno mismatch: found %d, expected $errno\n", errno);
+	printf("TEST FAILED: $desc\n");
+	stop();
+	raise(SIGKILL);
+	exit(1);
+}
+
+proc:::exit
+/pid == \$target/
+{
+	printf("TEST FAILED: $desc: exited normally\n");
+	exit(1);
+}
+EOF
+	if (( $? != 0 )); then
+		xsave_exit=1
+	fi
+}
+
+run_prog()
+{
+	typeset prog="$1"
+	typeset count=
+
+	printf "Beginning tests from %s\n" "$prog"
+	count=$($prog -c)
+	if (( $? != 0 || count <= 0)); then
+		warn "failed to get entry count for $prog"
+		return
+	fi
+
+	for ((i = 0; i < count; i++)) {
+		run_single $prog $i
+	}
+}
+
+run_prog $xsave_bad32
+run_prog $xsave_bad64
+
+if (( xsave_exit == 0 )); then
+	printf "All tests passed successfully\n"
+fi
+exit $xsave_exit
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsave_util.c	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,759 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This file implements various utility functions we use for the xsave tests.
+ */
+
+#include <string.h>
+#include <strings.h>
+#include <sys/auxv.h>
+#include <sys/sysmacros.h>
+#include <err.h>
+#include <stdlib.h>
+#include <procfs.h>
+#include <sys/x86_archext.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/debug.h>
+#include <ieeefp.h>
+
+#include "xsave_util.h"
+
+static uint_t xsu_proc_timeout = 60 * 1000; /* 60s in ms */
+
+/*
+ * Determine if we have the hardware support required for a given level of
+ * hardware support.
+ */
+uint32_t
+xsu_hwsupport(void)
+{
+	uint_t isa[3];
+	uint_t nisa = getisax(isa, ARRAY_SIZE(isa));
+
+	if (nisa != ARRAY_SIZE(isa)) {
+		errx(EXIT_FAILURE, "did not get all %u hwcap values, found %u",
+		    ARRAY_SIZE(isa), nisa);
+	}
+
+	if ((isa[0] & AV_386_XSAVE) == 0) {
+		errx(EXIT_FAILURE, "xsave not present: this test should have "
+		    "been skipped");
+	}
+
+	if ((isa[1] & AV_386_2_AVX512F) != 0) {
+		warnx("found %%zmm support");
+		return (XSU_ZMM);
+	}
+
+	if ((isa[0] & AV_386_AVX) != 0) {
+		warnx("found %%ymm support");
+		return (XSU_YMM);
+	}
+
+	errx(EXIT_FAILURE, "no non-XMM xsave state found: this test should "
+	    "have been skipped");
+}
+
+/*
+ * Fill all the valid regions of an FPU based on treating the vector register as
+ * a series of uint32_t values and going from there.
+ */
+void
+xsu_fill(xsu_fpu_t *fpu, uint32_t level, uint32_t start)
+{
+	(void) memset(fpu, 0, sizeof (xsu_fpu_t));
+
+	switch (level) {
+	default:
+		errx(EXIT_FAILURE, "given unknown xsu level: 0x%x", level);
+	case XSU_YMM:
+		for (uint32_t regno = 0; regno < XSU_MAX_YMM; regno++) {
+			for (uint32_t u32 = 0; u32 < XSU_YMM_U32; u32++,
+			    start++) {
+				fpu->xf_reg[regno]._l[u32] = start;
+			}
+		}
+		break;
+	case XSU_ZMM:
+		for (uint32_t regno = 0; regno < XSU_MAX_ZMM; regno++) {
+			for (uint32_t u32 = 0; u32 < XSU_ZMM_U32; u32++,
+			    start++) {
+				fpu->xf_reg[regno]._l[u32] = start;
+			}
+		}
+		for (uint32_t regno = 0; regno < ARRAY_SIZE(fpu->xf_opmask);
+		    regno++) {
+			uint64_t val = start | (((uint64_t)start + 1) << 32);
+			fpu->xf_opmask[regno] = val;
+			start += 2;
+		}
+		break;
+	}
+}
+
+static void
+xsu_overwrite_uctx_xmm(ucontext_t *uctx, const xsu_fpu_t *fpu)
+{
+	struct _fpchip_state *fp;
+
+	fp = &uctx->uc_mcontext.fpregs.fp_reg_set.fpchip_state;
+	for (uint32_t i = 0; i < XSU_MAX_XMM; i++) {
+		(void) memcpy(&fp->xmm[i], &fpu->xf_reg[i]._l[0],
+		    XSU_XMM_U32 * sizeof (uint32_t));
+	}
+}
+
+static void
+xsu_overwrite_uctx_ymm(uintptr_t arg, const xsu_fpu_t *fpu)
+{
+	prxregset_ymm_t *ymm = (void *)arg;
+
+	for (uint32_t i = 0; i < XSU_MAX_YMM; i++) {
+		(void) memcpy(&ymm->prx_ymm[i]._l[0],
+		    &fpu->xf_reg[i]._l[XSU_XMM_U32],
+		    XSU_XMM_U32 * sizeof (uint32_t));
+	}
+}
+
+static void
+xsu_overwrite_uctx_zmm(uintptr_t arg, const xsu_fpu_t *fpu)
+{
+	prxregset_zmm_t *zmm = (void *)arg;
+
+	/*
+	 * Because this is the low zmm registers, we actually use the max ymm
+	 * value as that's what actually fits in the low zmm and not the full
+	 * definition.
+	 */
+	for (uint32_t i = 0; i < XSU_MAX_YMM; i++) {
+		(void) memcpy(&zmm->prx_zmm[i]._l[0],
+		    &fpu->xf_reg[i]._l[XSU_YMM_U32],
+		    XSU_YMM_U32 * sizeof (uint32_t));
+	}
+}
+
+static void
+xsu_overwrite_uctx_hi_zmm(uintptr_t arg, const xsu_fpu_t *fpu)
+{
+#ifdef __amd64
+	prxregset_hi_zmm_t *zmm = (void *)arg;
+
+	for (uint32_t i = XSU_MAX_YMM; i < XSU_MAX_ZMM; i++) {
+		(void) memcpy(&zmm->prx_hi_zmm[i - XSU_MAX_YMM]._l[0],
+		    &fpu->xf_reg[i]._l[0],
+		    XSU_ZMM_U32 * sizeof (uint32_t));
+	}
+#else	/* !__amd64 */
+	warnx("attempted to set High ZMM registers on a 32-bit process!");
+	abort();
+#endif	/* __amd64 */
+}
+
+void
+xsu_overwrite_uctx(ucontext_t *uctx, const xsu_fpu_t *fpu, uint32_t hwsup)
+{
+	size_t xsave_size = sizeof (uc_xsave_t);
+	void *new_buf;
+	uc_xsave_t *ucs;
+	uintptr_t write_ptr;
+
+	if (hwsup != XSU_YMM && hwsup != XSU_ZMM) {
+		errx(EXIT_FAILURE, "given unknown xsu level: 0x%x", hwsup);
+	}
+
+	if (hwsup >= XSU_YMM) {
+		xsave_size += sizeof (prxregset_ymm_t);
+	}
+
+	if (hwsup >= XSU_ZMM) {
+		xsave_size += sizeof (prxregset_zmm_t);
+		xsave_size += sizeof (prxregset_opmask_t);
+		if (XSU_MAX_ZMM > 16) {
+			xsave_size += sizeof (prxregset_hi_zmm_t);
+		}
+	}
+
+	new_buf = calloc(1, xsave_size);
+	if (new_buf == NULL) {
+		errx(EXIT_FAILURE, "failed to allocate xsave buf");
+	}
+	ucs = new_buf;
+	ucs->ucx_vers = UC_XSAVE_VERS;
+	ucs->ucx_len = xsave_size;
+	if (hwsup >= XSU_YMM) {
+		ucs->ucx_bv |= XFEATURE_AVX;
+	}
+
+	if (hwsup >= XSU_ZMM) {
+		ucs->ucx_bv |= XFEATURE_AVX512_OPMASK | XFEATURE_AVX512_ZMM;
+		if (XSU_MAX_ZMM > 16)
+			ucs->ucx_bv |= XFEATURE_AVX512_HI_ZMM;
+	}
+
+	/*
+	 * At this point we have rigged things up. XMM values are in the
+	 * ucontext_t itself. After that we must write things out in the kernel
+	 * signal order. Note, the XMM state is not set in the bit-vector
+	 * because well, we don't actually use the xsave pieces for it because o
+	 * the ucontext_t ABI has the xmm state always there. See
+	 * uts/intel/os/fpu.c's big theory statement for more info.
+	 */
+	xsu_overwrite_uctx_xmm(uctx, fpu);
+	write_ptr = (uintptr_t)new_buf + sizeof (uc_xsave_t);
+	if (hwsup >= XSU_YMM) {
+		xsu_overwrite_uctx_ymm(write_ptr, fpu);
+		write_ptr += sizeof (prxregset_ymm_t);
+	}
+
+	if (hwsup >= XSU_ZMM) {
+		(void) memcpy((void *)write_ptr, fpu->xf_opmask,
+		    sizeof (fpu->xf_opmask));
+		write_ptr += sizeof (fpu->xf_opmask);
+		xsu_overwrite_uctx_zmm(write_ptr, fpu);
+		write_ptr += sizeof (prxregset_zmm_t);
+		if (XSU_MAX_ZMM > 16) {
+			xsu_overwrite_uctx_hi_zmm(write_ptr, fpu);
+			write_ptr += sizeof (prxregset_hi_zmm_t);
+		}
+	}
+
+	uctx->uc_xsave = (long)(uintptr_t)new_buf;
+}
+
+static boolean_t
+xsu_check_vector(const upad512_t *src, const upad512_t *chk, uint32_t regno,
+    uint32_t nu32)
+{
+	boolean_t valid = B_TRUE;
+
+	for (uint32_t i = 0; i < nu32; i++) {
+		if (src->_l[i] != chk->_l[i]) {
+			warnx("vec[%u] u32 %u differs: expected 0x%x, "
+			    "found 0x%x", regno, i, src->_l[i], chk->_l[i]);
+			valid = B_FALSE;
+		}
+	}
+
+	return (valid);
+}
+
+boolean_t
+xsu_same(const xsu_fpu_t *src, const xsu_fpu_t *check, uint32_t hwsup)
+{
+	boolean_t valid = B_TRUE;
+
+	switch (hwsup) {
+	default:
+		errx(EXIT_FAILURE, "given unkonwn xsu level: 0x%x", hwsup);
+	case XSU_YMM:
+		for (uint32_t i = 0; i < XSU_MAX_YMM; i++) {
+			if (!xsu_check_vector(&src->xf_reg[i],
+			    &check->xf_reg[i], i, XSU_YMM_U32)) {
+				valid = B_FALSE;
+			}
+		}
+		break;
+	case XSU_ZMM:
+		for (uint32_t i = 0; i < XSU_MAX_ZMM; i++) {
+			if (!xsu_check_vector(&src->xf_reg[i],
+			    &check->xf_reg[i], i, XSU_ZMM_U32)) {
+				valid = B_FALSE;
+			}
+		}
+		for (uint32_t i = 0; i < ARRAY_SIZE(src->xf_opmask); i++) {
+			if (src->xf_opmask[i] != check->xf_opmask[i]) {
+				warnx("mask[%u] differs: expected 0x%" PRIx64
+				    ", found 0x%" PRIx64, i, src->xf_opmask[i],
+				    check->xf_opmask[i]);
+				valid = B_FALSE;
+			}
+		}
+		break;
+	}
+	return (valid);
+}
+
+
+void *
+xsu_sleeper_thread(void *arg __unused)
+{
+	for (;;) {
+		(void) sleep(100);
+	}
+	return (NULL);
+}
+
+static void
+xsu_dump_vector(FILE *f, const upad512_t *reg, uint32_t nu32, const char *name,
+    uint32_t idx)
+{
+	VERIFY3U(nu32 % 4, ==, 0);
+	for (uint32_t i = 0; i < nu32; i += 4) {
+		(void) fprintf(f, "%s[%02u] [%02u:%02u] = { 0x%08x 0x%08x "
+		    "0x%08x 0x%08x }\n", name, idx, i + 3, i,  reg->_l[i + 3],
+		    reg->_l[i + 2], reg->_l[i + 1], reg->_l[i]);
+	}
+}
+
+void
+xsu_dump(FILE *f, const xsu_fpu_t *fpu, uint32_t hwsup)
+{
+
+	switch (hwsup) {
+	default:
+		errx(EXIT_FAILURE, "given unkonwn xsu level: 0x%x", hwsup);
+	case XSU_YMM:
+		for (uint32_t i = 0; i < XSU_MAX_YMM; i++) {
+			xsu_dump_vector(f, &fpu->xf_reg[i], XSU_YMM_U32,
+			    "ymm", i);
+		}
+		break;
+	case XSU_ZMM:
+		for (uint32_t i = 0; i < XSU_MAX_ZMM; i++) {
+			xsu_dump_vector(f, &fpu->xf_reg[i], XSU_ZMM_U32,
+			    "zmm", i);
+		}
+
+		for (uint32_t i = 0; i < ARRAY_SIZE(fpu->xf_opmask); i++) {
+			(void) fprintf(f, "%%k%u 0x%016" PRIx64"\n", i,
+			    fpu->xf_opmask[i]);
+		}
+		break;
+	}
+}
+
+typedef struct xsu_prx {
+	uint32_t xp_hwsup;
+	prxregset_xsave_t *xp_xsave;
+	prxregset_ymm_t *xp_ymm;
+	prxregset_opmask_t *xp_opmask;
+	prxregset_zmm_t *xp_zmm;
+	prxregset_hi_zmm_t *xp_hi_zmm;
+} xsu_prx_t;
+
+static void
+xsu_fpu_to_xregs_xsave(xsu_prx_t *prx, const xsu_fpu_t *fpu)
+{
+	prx->xp_xsave->prx_fx_fcw = FPU_CW_INIT;
+	prx->xp_xsave->prx_fx_mxcsr = SSE_MXCSR_INIT;
+	for (uint32_t i = 0; i < XSU_MAX_XMM; i++) {
+		(void) memcpy(&prx->xp_xsave->prx_fx_xmm[i],
+		    &fpu->xf_reg[i]._l[0], XSU_XMM_U32 * sizeof (uint32_t));
+	}
+
+	prx->xp_xsave->prx_xsh_xstate_bv = XFEATURE_LEGACY_FP |
+	    XFEATURE_SSE;
+	if (prx->xp_hwsup >= XSU_YMM) {
+		prx->xp_xsave->prx_xsh_xstate_bv |= XFEATURE_AVX;
+	}
+
+	if (prx->xp_hwsup >= XSU_ZMM) {
+		prx->xp_xsave->prx_xsh_xstate_bv |= XFEATURE_AVX512;
+	}
+}
+
+static void
+xsu_fpu_to_xregs_ymm(xsu_prx_t *prx, const xsu_fpu_t *fpu)
+{
+	/* Copy the upper 128-bits to the YMM save area */
+	for (uint32_t i = 0; i < XSU_MAX_YMM; i++) {
+		(void) memcpy(&prx->xp_ymm->prx_ymm[i],
+		    &fpu->xf_reg[i]._l[XSU_XMM_U32],
+		    XSU_XMM_U32 * sizeof (uint32_t));
+	}
+}
+
+static void
+xsu_fpu_to_xregs_zmm(xsu_prx_t *prx, const xsu_fpu_t *fpu)
+{
+	/* The lower 16 regs are only 256-bit, the upper are 512-bit */
+	for (uint32_t i = 0; i < MIN(XSU_MAX_ZMM, 16); i++) {
+		(void) memcpy(&prx->xp_zmm->prx_zmm[i],
+		    &fpu->xf_reg[i]._l[XSU_YMM_U32],
+		    XSU_YMM_U32 * sizeof (uint32_t));
+	}
+
+#ifdef __amd64
+	for (uint32_t i = 16; i < XSU_MAX_ZMM; i++) {
+		(void) memcpy(&prx->xp_hi_zmm->prx_hi_zmm[i - 16],
+		    &fpu->xf_reg[i]._l[0],
+		    XSU_ZMM_U32 * sizeof (uint32_t));
+	}
+#endif
+
+	(void) memcpy(prx->xp_opmask->prx_opmask, fpu->xf_opmask,
+	    sizeof (prx->xp_opmask->prx_opmask));
+}
+
+
+void
+xsu_fpu_to_xregs(const xsu_fpu_t *fpu, uint32_t hwsup, prxregset_t **prxp,
+    size_t *sizep)
+{
+	uint32_t ninfo = 1, curinfo;
+	size_t len = sizeof (prxregset_hdr_t) + sizeof (prxregset_info_t) +
+	    sizeof (prxregset_xsave_t);
+	prxregset_hdr_t *hdr;
+	uint32_t off;
+	xsu_prx_t prx;
+
+	if (hwsup != XSU_YMM && hwsup != XSU_ZMM) {
+		errx(EXIT_FAILURE, "given unkonwn xsu level: 0x%x", hwsup);
+	}
+
+	if (hwsup >= XSU_YMM) {
+		len += sizeof (prxregset_info_t) + sizeof (prxregset_ymm_t);
+		ninfo++;
+	}
+
+	if (hwsup >= XSU_ZMM) {
+		len += 3 * sizeof (prxregset_info_t) +
+		    sizeof (prxregset_opmask_t) + sizeof (prxregset_zmm_t) +
+		    sizeof (prxregset_hi_zmm_t);
+		ninfo += 3;
+	}
+
+	hdr = calloc(1, len);
+	if (hdr == NULL) {
+		err(EXIT_FAILURE, "failed to allocate prxregset_t (%zu bytes)",
+		    len);
+	}
+	(void) memset(&prx, 0, sizeof (prx));
+	prx.xp_hwsup = hwsup;
+
+#ifdef __amd64
+	VERIFY3U(len, <=, UINT32_MAX);
+#endif	/* __amd64 */
+	hdr->pr_type = PR_TYPE_XSAVE;
+	hdr->pr_size = (uint32_t)len;
+	hdr->pr_ninfo = ninfo;
+
+	curinfo = 0;
+	off = sizeof (prxregset_hdr_t) + sizeof (prxregset_info_t) * ninfo;
+	hdr->pr_info[curinfo].pri_type = PRX_INFO_XSAVE;
+	hdr->pr_info[curinfo].pri_size = sizeof (prxregset_xsave_t);
+	hdr->pr_info[curinfo].pri_offset = off;
+	prx.xp_xsave = (void *)((uintptr_t)hdr + off);
+	off += sizeof (prxregset_xsave_t);
+	curinfo++;
+
+	if (hwsup >= XSU_YMM) {
+		hdr->pr_info[curinfo].pri_type = PRX_INFO_YMM;
+		hdr->pr_info[curinfo].pri_size = sizeof (prxregset_ymm_t);
+		hdr->pr_info[curinfo].pri_offset = off;
+		prx.xp_ymm = (void *)((uintptr_t)hdr + off);
+		off += sizeof (prxregset_ymm_t);
+		curinfo++;
+	}
+
+	if (hwsup >= XSU_ZMM) {
+		hdr->pr_info[curinfo].pri_type = PRX_INFO_OPMASK;
+		hdr->pr_info[curinfo].pri_size = sizeof (prxregset_opmask_t);
+		hdr->pr_info[curinfo].pri_offset = off;
+		prx.xp_opmask = (void *)((uintptr_t)hdr + off);
+		off += sizeof (prxregset_opmask_t);
+		curinfo++;
+
+		hdr->pr_info[curinfo].pri_type = PRX_INFO_ZMM;
+		hdr->pr_info[curinfo].pri_size = sizeof (prxregset_zmm_t);
+		hdr->pr_info[curinfo].pri_offset = off;
+		prx.xp_zmm = (void *)((uintptr_t)hdr + off);
+		off += sizeof (prxregset_zmm_t);
+		curinfo++;
+
+		hdr->pr_info[curinfo].pri_type = PRX_INFO_HI_ZMM;
+		hdr->pr_info[curinfo].pri_size = sizeof (prxregset_hi_zmm_t);
+		hdr->pr_info[curinfo].pri_offset = off;
+		prx.xp_hi_zmm = (void *)((uintptr_t)hdr + off);
+		off += sizeof (prxregset_hi_zmm_t);
+		curinfo++;
+	}
+
+	xsu_fpu_to_xregs_xsave(&prx, fpu);
+	if (hwsup >= XSU_YMM) {
+		xsu_fpu_to_xregs_ymm(&prx, fpu);
+	}
+
+	if (hwsup >= XSU_ZMM) {
+		xsu_fpu_to_xregs_zmm(&prx, fpu);
+	}
+
+	*prxp = (prxregset_t *)hdr;
+	*sizep = len;
+}
+
+/*
+ * This pairs with xsu_proc_finish() below. The goal is to allow us to inject
+ * state after hitting a breakpoint, which is generally used right before
+ * something wants to print data.
+ */
+void
+xsu_proc_bkpt(xsu_proc_t *xp)
+{
+	int perr;
+	struct ps_prochandle *P;
+	char *const argv[3] = { xp->xp_prog, xp->xp_arg, NULL };
+	GElf_Sym sym;
+
+	P = Pcreate(xp->xp_prog, argv, &perr, NULL, 0);
+	if (P == NULL) {
+		errx(EXIT_FAILURE, "failed to create %s: %s", xp->xp_prog,
+		    Pcreate_error(perr));
+	}
+
+	xp->xp_proc = P;
+	(void) Punsetflags(P, PR_RLC);
+	if (Psetflags(P, PR_KLC | PR_BPTADJ) != 0) {
+		int e = errno;
+		Prelease(P, PRELEASE_KILL);
+		errc(EXIT_FAILURE, e, "failed to set PR_KLC | PR_BPTADJ flags");
+	}
+
+	if (Pxlookup_by_name(P, LM_ID_BASE, xp->xp_object, xp->xp_symname, &sym,
+	    NULL) != 0) {
+		err(EXIT_FAILURE, "failed to find %s`%s", xp->xp_object,
+		    xp->xp_symname);
+	}
+
+	if (Pfault(P, FLTBPT, 1) != 0) {
+		errx(EXIT_FAILURE, "failed to set the FLTBPT disposition");
+	}
+
+	xp->xp_addr = sym.st_value;
+	if (Psetbkpt(P, sym.st_value, &xp->xp_instr) != 0) {
+		err(EXIT_FAILURE, "failed to set breakpoint on xsu_getfpu "
+		    "(0x%" PRIx64 ")", sym.st_value);
+	}
+
+	if (Psetrun(P, 0, 0) != 0) {
+		err(EXIT_FAILURE, "failed to resume running our target");
+	}
+
+	if (Pwait(P, xsu_proc_timeout) != 0) {
+		err(EXIT_FAILURE, "%s did not hit our expected breakpoint",
+		    argv[1]);
+	}
+}
+
+/*
+ * Run a process to completion and get its wait exit status.
+ */
+void
+xsu_proc_finish(xsu_proc_t *xp)
+{
+	pid_t pid = Ppsinfo(xp->xp_proc)->pr_pid;
+
+	if (Pdelbkpt(xp->xp_proc, xp->xp_addr, xp->xp_instr) != 0) {
+		err(EXIT_FAILURE, "failed to delete %s`%s() breakpoint",
+		    xp->xp_object, xp->xp_symname);
+	}
+
+	if (Psetrun(xp->xp_proc, 0, PRCFAULT) != 0) {
+		err(EXIT_FAILURE, "failed to resume running our target");
+	}
+
+	if (waitpid(pid, &xp->xp_wait, 0) != pid) {
+		err(EXIT_FAILURE, "failed to get our child processes's wait "
+		    "info", pid);
+	}
+
+	if (WIFEXITED(xp->xp_wait) == 0) {
+		errx(EXIT_FAILURE, "our child process didn't actually exit!");
+	}
+
+	Pfree(xp->xp_proc);
+	xp->xp_proc = NULL;
+}
+
+void
+xsu_fpregset_xmm_set(fpregset_t *fpr, uint32_t seed)
+{
+	size_t nregs = ARRAY_SIZE(fpr->fp_reg_set.fpchip_state.xmm);
+	for (uint32_t i = 0; i < nregs; i++) {
+		upad128_t *u128 = &fpr->fp_reg_set.fpchip_state.xmm[i];
+		for (uint32_t u32 = 0; u32 < XSU_XMM_U32; u32++, seed++) {
+			u128->_l[u32] = seed;
+		}
+	}
+}
+
+void
+xsu_xregs_xmm_set(prxregset_t *prx, uint32_t seed)
+{
+	prxregset_hdr_t *hdr = (prxregset_hdr_t *)prx;
+	prxregset_xsave_t *xsave = NULL;
+
+	for (uint32_t i = 0; i < hdr->pr_ninfo; i++) {
+		if (hdr->pr_info[i].pri_type == PRX_INFO_XSAVE) {
+			xsave = (void *)((uintptr_t)prx +
+			    hdr->pr_info[i].pri_offset);
+			break;
+		}
+	}
+
+	if (xsave == NULL) {
+		errx(EXIT_FAILURE, "asked to set xsave %%xmm regs, but no "
+		    "xsave info present");
+	}
+
+	size_t nregs = ARRAY_SIZE(xsave->prx_fx_xmm);
+	for (uint32_t i = 0; i < nregs; i++) {
+		for (uint32_t u32 = 0; u32 < XSU_XMM_U32; u32++, seed++) {
+			xsave->prx_fx_xmm[i]._l[u32] = seed;
+		}
+	}
+}
+
+static const prxregset_info_t *
+xsu_xregs_find_comp(const prxregset_hdr_t *hdr, uint32_t comp, uintptr_t *datap)
+{
+	for (uint32_t i = 0; i < hdr->pr_ninfo; i++) {
+		if (hdr->pr_info[i].pri_type == comp) {
+			*datap = (uintptr_t)hdr + hdr->pr_info[i].pri_offset;
+			return (&hdr->pr_info[i]);
+		}
+	}
+
+	return (NULL);
+}
+
+boolean_t
+xsu_xregs_comp_equal(const prxregset_t *src, const prxregset_t *dest,
+    uint32_t comp)
+{
+	const prxregset_hdr_t *shdr = (prxregset_hdr_t *)src;
+	const prxregset_hdr_t *dhdr = (prxregset_hdr_t *)dest;
+	const prxregset_info_t *sinfo = NULL, *dinfo = NULL;
+	uintptr_t sdata, ddata;
+
+	sinfo = xsu_xregs_find_comp(shdr, comp, &sdata);
+	if (sinfo == NULL) {
+		warnx("source xregs missing component %u", comp);
+		return (B_FALSE);
+	}
+
+	dinfo = xsu_xregs_find_comp(dhdr, comp, &ddata);
+	if (dinfo == NULL) {
+		warnx("destination xregs missing component %u", comp);
+		return (B_FALSE);
+	}
+
+	if (sinfo->pri_size != dinfo->pri_size) {
+		warnx("source xregs length 0x%x does not match dest xregs 0x%x",
+		    sinfo->pri_size, dinfo->pri_size);
+	}
+
+	if (bcmp((void *)sdata, (void *)ddata, sinfo->pri_size) != 0) {
+		warnx("component data differs: dumping!");
+		for (uint32_t i = 0; i < sinfo->pri_offset; i++) {
+			const uint8_t *su8 = (uint8_t *)sdata;
+			const uint8_t *du8 = (uint8_t *)ddata;
+
+			if (su8[i] != du8[i]) {
+				(void) fprintf(stderr,
+				    "src[%u] = 0x%2x\tdst[%u] = 0x%x\n",
+				    i, su8[i], i, du8[i]);
+			}
+		}
+
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+boolean_t
+xsu_fpregs_cmp(const fpregset_t *fpr, const prxregset_t *prx)
+{
+	boolean_t valid = B_TRUE;
+	const prxregset_hdr_t *hdr = (prxregset_hdr_t *)prx;
+	const prxregset_xsave_t *xsave = NULL;
+	uint16_t fpr_cw, fpr_sw;
+
+	for (uint32_t i = 0; i < hdr->pr_ninfo; i++) {
+		if (hdr->pr_info[i].pri_type == PRX_INFO_XSAVE) {
+			xsave = (void *)((uintptr_t)prx +
+			    hdr->pr_info[i].pri_offset);
+			break;
+		}
+	}
+
+	if (xsave == NULL) {
+		warnx("xregs missing xsave component for fpregs comparison");
+		return (B_FALSE);
+	}
+
+	/*
+	 * First check the XMM registers because those don't require ifdefs,
+	 * thankfully.
+	 */
+	size_t nregs = ARRAY_SIZE(fpr->fp_reg_set.fpchip_state.xmm);
+	for (size_t i = 0; i < nregs; i++) {
+		const upad128_t *u128 = &fpr->fp_reg_set.fpchip_state.xmm[i];
+		for (uint32_t u32 = 0; u32 < XSU_XMM_U32; u32++) {
+			if (u128->_l[u32] != xsave->prx_fx_xmm[i]._l[u32]) {
+				valid = B_FALSE;
+				(void) fprintf(stderr, "fpregset xmm[%u] "
+				    "u32[%u] does not match xsave, fpregset: "
+				    "0x%x, xsave: 0x%x\n", i, u32,
+				    u128->_l[u32],
+				    xsave->prx_fx_xmm[i]._l[u32]);
+			}
+		}
+	}
+
+	if (xsave->prx_fx_mxcsr != fpr->fp_reg_set.fpchip_state.mxcsr) {
+		valid = B_FALSE;
+		(void) fprintf(stderr, "mxcsr mismatched: fpregset: 0x%x, "
+		    "xsave: 0x%x\n", fpr->fp_reg_set.fpchip_state.mxcsr,
+		    xsave->prx_fx_mxcsr);
+	}
+
+	/*
+	 * Extract the basic x87 state. This requires ifdefs because the 32-bit
+	 * ABI here is a bit, particular. The 32-bit fpregs is the mcontext_t
+	 * struct which is mostly opaque and we need to use the ieeefp.h types
+	 * which are only visible for ILP32. It also treats 16-bit values as
+	 * 32-bit ones, hence masking below.
+	 */
+#ifdef __amd64
+	fpr_cw = fpr->fp_reg_set.fpchip_state.cw;
+	fpr_sw = fpr->fp_reg_set.fpchip_state.sw;
+#else	/* !__amd64 (__i386) */
+	struct _fpstate fps;
+
+	(void) memcpy(&fps, &fpr->fp_reg_set.fpchip_state, sizeof (fps));
+	fpr_cw = fps.cw & 0xffff;
+	fpr_sw = fps.sw & 0xffff;
+#endif	/* __amd64 */
+
+	if (fpr_cw != xsave->prx_fx_fcw) {
+		valid = B_FALSE;
+		(void) fprintf(stderr, "x87 cw mismatched: fpregset: 0x%x, "
+		    "xsave: 0x%x\n", fpr_cw, xsave->prx_fx_fcw);
+	}
+
+	if (fpr_sw != xsave->prx_fx_fsw) {
+		valid = B_FALSE;
+		(void) fprintf(stderr, "x87 sw mismatched: fpregset: 0x%x, "
+		    "xsave: 0x%x\n", fpr_sw, xsave->prx_fx_fsw);
+	}
+
+	return (valid);
+}
--- /dev/null	Wed Apr 12 20:33:29 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsave_util.h	Wed Apr 12 20:33:29 2023
@@ -0,0 +1,186 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+#ifndef _XSAVE_UTIL_H
+#define	_XSAVE_UTIL_H
+
+/*
+ * This file contains misc. pieces for use between our tests. This is
+ * implemented in both xsave_util.c and xsave_asm32.s and xsave_asm64.s.
+ */
+
+#ifndef	_ASM
+#include <sys/types.h>
+#include <stdint.h>
+#include <ucontext.h>
+#include <stdio.h>
+#include <procfs.h>
+#include <libproc.h>
+#endif	/* !_ASM */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * While we would prefer an enum, this is a macro so it can be shared with the
+ * assembler code.
+ */
+#define	XSU_XMM	0
+#define	XSU_YMM	1
+#define	XSU_ZMM	2
+
+/*
+ * These are definitions that vary based on whether we're in an ILP32 or LP64
+ * environment because of how the ISA works.
+ */
+#ifdef __amd64
+#define	XSU_MAX_XMM	16
+#define	XSU_MAX_YMM	16
+#define	XSU_MAX_ZMM	32
+#else
+#define	XSU_MAX_XMM	8
+#define	XSU_MAX_YMM	8
+#define	XSU_MAX_ZMM	8
+#endif
+
+#define	XSU_XMM_U32	4
+#define	XSU_YMM_U32	8
+#define	XSU_ZMM_U32	16
+
+#ifndef	_ASM
+
+/*
+ * Redefine the xsave header here for the test cases so we can avoid exposing
+ * this out of the kernel. Right now the uc_xsave_t isn't defined under
+ * _KMEMUSER and we're trying to hold onto that (or a similar style solution)
+ * while we can.
+ */
+#define	UC_XSAVE_VERS	(('u' << 24) | ('c' << 16) | 0x01)
+typedef struct uc_xsave {
+	uint32_t ucx_vers;
+	uint32_t ucx_len;
+	uint64_t ucx_bv;
+} uc_xsave_t;
+
+/*
+ * This structure represents a generic AVX-512 FPU state that can hold up to 32
+ * registers. If only XMM or YMM are valid, then the first 8 and 16 bytes will
+ * be valid respectively and the latter 16 entries won't be used in 64-bit code.
+ * In 32-bit code only the valid FPU entries will present.
+ */
+typedef struct xsu_fpu {
+	upad512_t	xf_reg[32];
+	uint64_t	xf_opmask[8];
+} xsu_fpu_t;
+
+extern uint32_t xsu_hwsupport(void);
+
+/*
+ * This deterministically fills the contents of an FPU structure. It will zero
+ * the entire structure and then fill in the appropriate parts based on the type
+ * of FPU we have.
+ */
+extern void xsu_fill(xsu_fpu_t *, uint32_t, uint32_t);
+
+/*
+ * Dump the contents of the FPU in a deterministic fashion to a specified file.
+ */
+extern void xsu_dump(FILE *, const xsu_fpu_t *, uint32_t);
+
+/*
+ * These routines read and write the state of the FPU. This will cover the
+ * selected hardware bits.
+ */
+extern void xsu_setfpu(const xsu_fpu_t *, uint32_t);
+extern void xsu_getfpu(xsu_fpu_t *, uint32_t);
+
+/*
+ * This is used to overwrite the contents of a ucontext_t with the resulting FPU
+ * state. The xc_xsave will be replaced with a pointer to something of our own
+ * sizing.
+ */
+extern void xsu_overwrite_uctx(ucontext_t *, const xsu_fpu_t *, uint32_t);
+
+/*
+ * Diff two different fpu sets and see if they're identical or not. Only the
+ * bytes that correspond to the indicated pieces will be checked.
+ */
+extern boolean_t xsu_same(const xsu_fpu_t *, const xsu_fpu_t *, uint32_t);
+
+/*
+ * This function allocates and sets up the prxregset_hdr_t and associated notes
+ * with their expected values based on the hardware support values.
+ */
+extern void xsu_xregs_alloc(void **, size_t *, uint32_t);
+
+/*
+ * This is a common function that just sleeps and is meant to be here for test
+ * programs that want a thread to mess with.
+ */
+extern void *xsu_sleeper_thread(void *);
+
+/*
+ * Convert a given xsu_fpu_t state into something that we can set via xregs.
+ */
+extern void xsu_fpu_to_xregs(const xsu_fpu_t *, uint32_t, prxregset_t **,
+    size_t *);
+
+typedef struct xsu_proc {
+	char *xp_prog;
+	char *xp_arg;
+	const char *xp_object;
+	const char *xp_symname;
+	struct ps_prochandle *xp_proc;
+	uintptr_t xp_addr;
+	ulong_t xp_instr;
+	int xp_wait;
+} xsu_proc_t;
+
+/*
+ * This pair of functions gives us a mini-debugging context. The first sets up a
+ * program that is ready and paused at a breakpoint.
+ */
+extern void xsu_proc_bkpt(xsu_proc_t *);
+extern void xsu_proc_finish(xsu_proc_t *);
+
+/*
+ * Set the xmm portion of an fpregset based on a seed.
+ */
+extern void xsu_fpregset_xmm_set(fpregset_t *, uint32_t);
+extern void xsu_xregs_xmm_set(prxregset_t *, uint32_t);
+
+/*
+ * Go through and see if the data in an fpregs section is equivalent to an xregs
+ * XMM section. This focuses on the control words and xmm data, we do not bother
+ * with the x87 registers themselves and not all of the 32-bit pieces.
+ */
+extern boolean_t xsu_fpregs_cmp(const fpregset_t *, const prxregset_t *);
+
+/*
+ * Given two xregs structures, check if the given component in them has
+ * identical data. Note, this assumes that the structure is valid-ish. That is,
+ * that all the info structures point to valid data.
+ */
+extern boolean_t xsu_xregs_comp_equal(const prxregset_t *, const prxregset_t *,
+    uint32_t);
+
+#endif	/* !_ASM */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _XSAVE_UTIL_H */
--- /dev/null	Wed Apr 12 20:33:30 2023
+++ new/usr/src/test/os-tests/tests/xsave/xsu_hwtype.c	Wed Apr 12 20:33:30 2023
@@ -0,0 +1,42 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
+/*
+ * This is meant for scripts to determine what kind of hardware support we
+ * actually have for output files and related.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "xsave_util.h"
+
+int
+main(void)
+{
+	uint32_t hwsup = xsu_hwsupport();
+
+	switch (hwsup) {
+	case XSU_YMM:
+		(void) printf("ymm\n");
+		break;
+	case XSU_ZMM:
+		(void) printf("zmm\n");
+		break;
+	default:
+		return (EXIT_FAILURE);
+	}
+
+	return (EXIT_SUCCESS);
+}
--- old/usr/src/tools/scripts/cstyle.pl	Wed Apr 12 20:33:30 2023
+++ new/usr/src/tools/scripts/cstyle.pl	Wed Apr 12 20:33:30 2023
@@ -21,7 +21,7 @@
 #
 # Copyright 2015 Toomas Soome <tsoome@me.com>
 # Copyright 2016 Nexenta Systems, Inc.
-# Copyright 2020 Oxide Computer Company
+# Copyright 2023 Oxide Computer Company
 #
 # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
@@ -613,7 +613,7 @@
 	if (/\S   *(&&|\|\|)/ || /(&&|\|\|)   *\S/) {
 		err("more than one space around boolean operator");
 	}
-	if (/\b(for|if|while|switch|sizeof|return|case)\(/) {
+	if (/\b(for|if|while|switch|sizeof|alignof|return|case)\(/) {
 		err("missing space between keyword and paren");
 	}
 	if (/(\b(for|if|while|switch|return)\b.*){2,}/ && !/^#define/) {
@@ -620,7 +620,7 @@
 		# multiple "case" and "sizeof" allowed
 		err("more than one keyword on line");
 	}
-	if (/\b(for|if|while|switch|sizeof|return|case)\s\s+\(/ &&
+	if (/\b(for|if|while|switch|sizeof|alignof|return|case)\s\s+\(/ &&
 	    !/^#if\s+\(/) {
 		err("extra space between keyword and paren");
 	}
@@ -629,7 +629,7 @@
 	if (/\w\s\(/) {
 		my $s = $_;
 		# strip off all keywords on the line
-		s/\b(for|if|while|switch|return|case|sizeof)\s\(/XXX(/g;
+		s/\b(for|if|while|switch|return|case|alignof|sizeof)\s\(/XXX(/g;
 		s/#elif\s\(/XXX(/g;
 		s/^#define\s+\w+\s+\(/XXX(/;
 		# do not match things like "void (*f)();"
@@ -662,6 +662,9 @@
 	if (/\bsizeof\b/ && !/\bsizeof\s*\(.*\)/) {
 		err("unparenthesized sizeof expression");
 	}
+	if (/\balignof\b/ && !/\balignof\s*\(.*\)/) {
+		err("unparenthesized alignof expression");
+	}
 	if (/\(\s/) {
 		err("whitespace after left paren");
 	}
--- old/usr/src/uts/common/exec/elf/elf_notes.c	Wed Apr 12 20:33:30 2023
+++ new/usr/src/uts/common/exec/elf/elf_notes.c	Wed Apr 12 20:33:30 2023
@@ -28,7 +28,7 @@
  * Copyright 2012 DEY Storage Systems, Inc.  All rights reserved.
  * Copyright 2018 Joyent, Inc.
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
- * Copyright 2022 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <sys/types.h>
@@ -138,7 +138,7 @@
 		v[0].p_filesz += sizeof (Note) + roundup(size, sizeof (Word));
 #endif	/* __i386_COMPAT */
 
-	if ((size = prhasx(p)? prgetprxregsize(p) : 0) != 0)
+	if ((size = prhasx(p) ? prgetprxregsize(p) : 0) != 0)
 		v[0].p_filesz += nlwp * sizeof (Note)
 		    + nlwp * roundup(size, sizeof (Word));
 
@@ -197,7 +197,7 @@
 		prupanic_t	upanic;
 	} *bigwad;
 
-	size_t xregsize = prhasx(p)? prgetprxregsize(p) : 0;
+	size_t xregsize = prhasx(p) ? prgetprxregsize(p) : 0;
 	size_t crsize = sizeof (prcred_t) + sizeof (gid_t) * (ngroups_max - 1);
 	size_t psize = prgetprivsize();
 	size_t bigsize = MAX(psize, MAX(sizeof (*bigwad),
@@ -574,7 +574,7 @@
 #endif /* __sparc */
 
 		if (xregsize) {
-			prgetprxregs(lwp, bigwad->xregs);
+			prgetprxregs(lwp, (prxregset_t *)bigwad->xregs);
 			error = elfnote(vp, &offset, NT_PRXREG,
 			    xregsize, bigwad->xregs, rlimit, credp);
 			if (error)
--- old/usr/src/uts/common/exec/elf/old_notes.c	Wed Apr 12 20:33:30 2023
+++ new/usr/src/uts/common/exec/elf/old_notes.c	Wed Apr 12 20:33:30 2023
@@ -57,11 +57,16 @@
 extern void oprgetstatus(kthread_t *, prstatus_t *, zone_t *);
 extern void oprgetpsinfo(proc_t *, prpsinfo_t *, kthread_t *);
 
+/*
+ * Historically the system dumped the xreg note when on SPARC. Because we no
+ * longer support SPARC we do not dump the old note form of the xregs for any
+ * additional platforms. Please do not add this back unless it's for SPARC's
+ * future resurrection.
+ */
 void
 setup_old_note_header(Phdr *v, proc_t *p)
 {
 	int nlwp = p->p_lwpcnt;
-	size_t size;
 
 	v[0].p_type = PT_NOTE;
 	v[0].p_flags = PF_R;
@@ -71,30 +76,10 @@
 	    + roundup(__KERN_NAUXV_IMPL * sizeof (aux_entry_t),
 	    sizeof (Word))
 	    + nlwp * roundup(sizeof (prstatus_t), sizeof (Word));
-	if (prhasfp())
-		v[0].p_filesz += nlwp * sizeof (Note)
-		    + nlwp*roundup(sizeof (prfpregset_t), sizeof (Word));
-	if ((size = prhasx(p)? prgetprxregsize(p) : 0) != 0)
-		v[0].p_filesz += nlwp * sizeof (Note)
-		    + nlwp * roundup(size, sizeof (Word));
-
-#if defined(__sparc)
-	/*
-	 * Figure out the number and sizes of register windows.
-	 */
-	{
-		kthread_t *t = p->p_tlist;
-		do {
-			if ((size = prnwindows(ttolwp(t))) != 0) {
-				size = sizeof (gwindows_t) -
-				    (SPARC_MAXREGWINDOW - size) *
-				    sizeof (struct rwindow);
-				v[0].p_filesz += sizeof (Note) +
-				    roundup(size, sizeof (Word));
-			}
-		} while ((t = t->t_forw) != p->p_tlist);
+	if (prhasfp()) {
+		v[0].p_filesz += nlwp * sizeof (Note) +
+		    nlwp * roundup(sizeof (prfpregset_t), sizeof (Word));
 	}
-#endif /* __sparc */
 }
 
 int
@@ -105,14 +90,9 @@
 		prpsinfo_t	psinfo;
 		prstatus_t	prstat;
 		prfpregset_t	fpregs;
-#if defined(__sparc)
-		gwindows_t	gwindows;
-#endif /* __sparc */
-		char		xregs[1];
 		aux_entry_t	auxv[__KERN_NAUXV_IMPL];
 	} *bigwad;
-	int xregsize = prhasx(p)? prgetprxregsize(p) : 0;
-	size_t bigsize = MAX(sizeof (*bigwad), (size_t)xregsize);
+	size_t bigsize = sizeof (*bigwad);
 	kthread_t *t;
 	klwp_t *lwp;
 	user_t *up;
@@ -199,35 +179,6 @@
 			    rlimit, credp);
 			if (error)
 				goto done;
-		}
-
-#if defined(__sparc)
-		/*
-		 * Unspilled SPARC register windows.
-		 */
-		{
-			size_t size = prnwindows(lwp);
-
-			if (size != 0) {
-				size = sizeof (gwindows_t) -
-				    (SPARC_MAXREGWINDOW - size) *
-				    sizeof (struct rwindow);
-				prgetwindows(lwp, &bigwad->gwindows);
-				error = elfnote(vp, &offset, NT_GWINDOWS,
-				    size, (caddr_t)&bigwad->gwindows,
-				    rlimit, credp);
-				if (error)
-					goto done;
-			}
-		}
-#endif /* __sparc */
-
-		if (xregsize) {
-			prgetprxregs(lwp, bigwad->xregs);
-			error = elfnote(vp, &offset, NT_PRXREG,
-			    xregsize, bigwad->xregs, rlimit, credp);
-			if (error)
-				goto done;
 		}
 	} while ((t = t->t_forw) != curthread);
 	ASSERT(nlwp == 0);
--- old/usr/src/uts/common/fs/proc/prcontrol.c	Wed Apr 12 20:33:30 2023
+++ new/usr/src/uts/common/fs/proc/prcontrol.c	Wed Apr 12 20:33:30 2023
@@ -26,6 +26,7 @@
 
 /*
  * Copyright 2015, Joyent, Inc.
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <sys/types.h>
@@ -60,13 +61,11 @@
 #include <vm/seg.h>
 #include <fs/proc/prdata.h>
 #include <sys/contract/process_impl.h>
+#include <sys/stdalign.h>
 
 static	void	pr_settrace(proc_t *, sigset_t *);
 static	int	pr_setfpregs(prnode_t *, prfpregset_t *);
-#if defined(__sparc)
 static	int	pr_setxregs(prnode_t *, prxregset_t *);
-static	int	pr_setasrs(prnode_t *, asrset_t);
-#endif
 static	int	pr_setvaddr(prnode_t *, caddr_t);
 static	int	pr_clearsig(prnode_t *);
 static	int	pr_clearflt(prnode_t *);
@@ -79,6 +78,13 @@
 static	void	pauselwps(proc_t *);
 static	void	unpauselwps(proc_t *);
 
+/*
+ * This union represents the size of commands that are generally fixed size in
+ * /proc. There are some commands that are variable size because the actual data
+ * is structured. Of things in the latter category, some of these are the same
+ * across all architectures (e.g. prcred_t, prpriv_t) and some vary and are
+ * opaque (e.g. the prxregset_t).
+ */
 typedef union {
 	long		sig;		/* PCKILL, PCUNKILL */
 	long		nice;		/* PCNICE */
@@ -91,10 +97,6 @@
 	sysset_t	sysset;		/* PCSENTRY, PCSEXIT */
 	prgregset_t	prgregset;	/* PCSREG, PCAGENT */
 	prfpregset_t	prfpregset;	/* PCSFPREG */
-#if defined(__sparc)
-	prxregset_t	prxregset;	/* PCSXREG */
-	asrset_t	asrset;		/* PCSASRS */
-#endif
 	prwatch_t	prwatch;	/* PCWATCH */
 	priovec_t	priovec;	/* PCREAD, PCWRITE */
 	prcred_t	prcred;		/* PCSCRED */
@@ -102,198 +104,472 @@
 	long		przoneid;	/* PCSZONE */
 } arg_t;
 
-static	int	pr_control(long, arg_t *, prnode_t *, cred_t *);
+static boolean_t
+prwritectl_pcscredx_sizef(const void *datap, size_t *sizep)
+{
+	const prcred_t *cred = datap;
 
-static size_t
-ctlsize(long cmd, size_t resid, arg_t *argp)
+	if (cred->pr_ngroups < 0 || cred->pr_ngroups > ngroups_max) {
+		return (B_FALSE);
+	}
+
+	if (cred->pr_ngroups == 0) {
+		*sizep = 0;
+	} else {
+		*sizep = (cred->pr_ngroups - 1) * sizeof (gid_t);
+	}
+	return (B_TRUE);
+}
+
+static boolean_t
+prwritectl_pcspriv_sizef(const void *datap, size_t *sizep)
 {
-	size_t size = sizeof (long);
-	size_t rnd;
-	int ngrp;
+	const prpriv_t *priv = datap;
+	*sizep = priv_prgetprivsize(priv) - sizeof (prpriv_t);
+	return (B_TRUE);
+}
 
-	switch (cmd) {
-	case PCNULL:
-	case PCSTOP:
-	case PCDSTOP:
-	case PCWSTOP:
-	case PCCSIG:
-	case PCCFAULT:
-		break;
-	case PCSSIG:
-		size += sizeof (siginfo_t);
-		break;
-	case PCTWSTOP:
-		size += sizeof (long);
-		break;
-	case PCKILL:
-	case PCUNKILL:
-	case PCNICE:
-		size += sizeof (long);
-		break;
-	case PCRUN:
-	case PCSET:
-	case PCUNSET:
-		size += sizeof (ulong_t);
-		break;
-	case PCSVADDR:
-		size += sizeof (caddr_t);
-		break;
-	case PCSTRACE:
-	case PCSHOLD:
-		size += sizeof (sigset_t);
-		break;
-	case PCSFAULT:
-		size += sizeof (fltset_t);
-		break;
-	case PCSENTRY:
-	case PCSEXIT:
-		size += sizeof (sysset_t);
-		break;
-	case PCSREG:
-	case PCAGENT:
-		size += sizeof (prgregset_t);
-		break;
-	case PCSFPREG:
-		size += sizeof (prfpregset_t);
-		break;
-#if defined(__sparc)
-	case PCSXREG:
-		size += sizeof (prxregset_t);
-		break;
-	case PCSASRS:
-		size += sizeof (asrset_t);
-		break;
+/*
+ * This structure represents a single /proc write command that we support and
+ * metadata about how to ensure we have sufficient data for it. To determine the
+ * data that we need to read, this combines information from three different
+ * sources for a given named command in 'pcs_cmd'. The main goal is to first
+ * make sure we have the right minimum amount of information so we can read and
+ * validate the data around variable length structures.
+ *
+ *   o Most commands have a fixed static size. This is represented in the
+ *     pcs_size member. This also is used to represent the base structure size
+ *     in the case of entries like PCSCREDX.
+ *
+ *   o Other commands have an unknown minimum size to determine how much data
+ *     there is and they use the pcs_minf() function to determine the right
+ *     value. This is often unknown at compile time because it is say a
+ *     machdep or ISA based feature (ala PCSXREGS) and we'd rather not #ifdef
+ *     this code to death. This may be skipped and is for most things. The value
+ *     it returns is added to the static value.
+ *
+ *   o The final piece is the pcs_sizef() function pointer which determines the
+ *     total required size for this. It is given a pointer that has at least
+ *     pcs_size and pcs_minf() bytes. This is used to determine the total
+ *     expected size of the structure. Callers must not dereference data beyond
+ *     what they've indicated previously. This should only return exra bytes
+ *     that are required beyond what was already indicated between the two
+ *     functions.
+ *
+ * In all cases, the core prwritectl() logic will determine if there is
+ * sufficient step along the way for each of these to proceed.
+ */
+typedef struct proc_control_info {
+	long	pcs_cmd;
+	size_t	pcs_size;
+	boolean_t (*pcs_minf)(size_t *);
+	boolean_t (*pcs_sizef)(const void *, size_t *);
+} proc_control_info_t;
+
+static const proc_control_info_t proc_ctl_info[] = {
+	{ PCNULL, 0, NULL, NULL },
+	{ PCSTOP, 0, NULL, NULL },
+	{ PCDSTOP, 0, NULL, NULL },
+	{ PCWSTOP, 0, NULL, NULL },
+	{ PCCSIG, 0, NULL, NULL },
+	{ PCCFAULT, 0, NULL, NULL },
+	{ PCSSIG, sizeof (siginfo_t), NULL, NULL },
+	{ PCTWSTOP, sizeof (long), NULL, NULL },
+	{ PCKILL, sizeof (long), NULL, NULL },
+	{ PCUNKILL, sizeof (long), NULL, NULL },
+	{ PCNICE, sizeof (long), NULL, NULL },
+	{ PCRUN, sizeof (ulong_t), NULL, NULL },
+	{ PCSET, sizeof (ulong_t), NULL, NULL },
+	{ PCUNSET, sizeof (ulong_t), NULL, NULL },
+	{ PCSTRACE, sizeof (sigset_t), NULL, NULL },
+	{ PCSHOLD, sizeof (sigset_t), NULL, NULL },
+	{ PCSFAULT, sizeof (fltset_t), NULL, NULL },
+	{ PCSENTRY, sizeof (sysset_t), NULL, NULL },
+	{ PCSEXIT, sizeof (sysset_t), NULL, NULL },
+	{ PCSREG, sizeof (prgregset_t), NULL, NULL },
+	{ PCAGENT, sizeof (prgregset_t), NULL, NULL },
+	{ PCSFPREG, sizeof (prfpregset_t), NULL, NULL },
+	{ PCSXREG, 0, prwriteminxreg, prwritesizexreg },
+	{ PCWATCH, sizeof (prwatch_t), NULL },
+	{ PCREAD, sizeof (priovec_t), NULL, NULL },
+	{ PCWRITE, sizeof (priovec_t), NULL, NULL },
+	{ PCSCRED, sizeof (prcred_t), NULL, NULL },
+	{ PCSCREDX, sizeof (prcred_t), NULL, prwritectl_pcscredx_sizef },
+	{ PCSPRIV, sizeof (prpriv_t), NULL, prwritectl_pcspriv_sizef },
+	{ PCSZONE, sizeof (long), NULL },
+};
+
+/*
+ * We need a default buffer that we're going to allocate when we need memory to
+ * read control operations. This is on average large enough to hold multiple
+ * control operations. We leave this as a smaller value on debug builds just
+ * to exercise our reallocation logic.
+ */
+#ifdef	DEBUG
+#define	PROC_CTL_DEFSIZE	32
+#else
+#define	PROC_CTL_DEFSIZE	1024
 #endif
-	case PCWATCH:
-		size += sizeof (prwatch_t);
-		break;
-	case PCREAD:
-	case PCWRITE:
-		size += sizeof (priovec_t);
-		break;
-	case PCSCRED:
-		size += sizeof (prcred_t);
-		break;
-	case PCSCREDX:
-		/*
-		 * We cannot derefence the pr_ngroups fields if it
-		 * we don't have enough data.
-		 */
-		if (resid < size + sizeof (prcred_t) - sizeof (gid_t))
-			return (0);
-		ngrp = argp->prcred.pr_ngroups;
-		if (ngrp < 0 || ngrp > ngroups_max)
-			return (0);
 
-		/* The result can be smaller than sizeof (prcred_t) */
-		size += sizeof (prcred_t) - sizeof (gid_t);
-		size += ngrp * sizeof (gid_t);
-		break;
-	case PCSPRIV:
-		if (resid >= size + sizeof (prpriv_t))
-			size += priv_prgetprivsize(&argp->prpriv);
-		else
-			return (0);
-		break;
-	case PCSZONE:
-		size += sizeof (long);
-		break;
-	default:
+/*
+ * This structure is used to track all of the information that we have around a
+ * prwritectl call. This is used to reduce function parameters and make state
+ * clear.
+ */
+typedef struct {
+	void	*prwc_buf;
+	size_t	prwc_buflen;
+	size_t	prwc_curvalid;
+	uio_t	*prwc_uiop;
+	prnode_t *prwc_pnp;
+	boolean_t prwc_locked;
+	boolean_t prwc_need32;
+	void	*prwc_buf32;
+} prwritectl_t;
+
+/*
+ * Attempt to read in at least needed data. If we need to read in data, then we
+ * will try to fill in as much data as required.
+ */
+static int
+prwritectl_readin(prwritectl_t *prwc, size_t needed)
+{
+	int ret;
+	size_t toread;
+	void *start;
+
+	/*
+	 * If we have as much data as we need then we're good to go.
+	 */
+	if (prwc->prwc_curvalid > needed) {
+		ASSERT3U(prwc->prwc_buflen, >=, prwc->prwc_curvalid);
+		ASSERT3U(prwc->prwc_buflen, >=, needed);
 		return (0);
 	}
 
-	/* Round up to a multiple of long, unless exact amount written */
-	if (size < resid) {
-		rnd = size & (sizeof (long) - 1);
+	/*
+	 * We don't have all of our data. We must make sure of several things:
+	 *
+	 *   1. That there actually is enough data in the uio_t for what we
+	 *	need, considering what we've already read.
+	 *   2. If the process is locked, at this point, we want to unlock it
+	 *	before we deal with any I/O or memory allocation. Otherwise we
+	 *	can wreak havoc with p_lock / paging.
+	 *   3. We need to make sure that our buffer is large enough to actually
+	 *	fit it all.
+	 *   4. Only at that point can we actually perform the read.
+	 */
+	if (needed - prwc->prwc_curvalid > prwc->prwc_uiop->uio_resid) {
+		return (EINVAL);
+	}
 
-		if (rnd != 0)
-			size += sizeof (long) - rnd;
+	if (prwc->prwc_locked) {
+		prunlock(prwc->prwc_pnp);
+		prwc->prwc_locked = B_FALSE;
 	}
 
-	if (size > resid)
-		return (0);
-	return (size);
+	if (needed > prwc->prwc_buflen) {
+		size_t new_len = P2ROUNDUP(needed, PROC_CTL_DEFSIZE);
+		prwc->prwc_buf = kmem_rezalloc(prwc->prwc_buf,
+		    prwc->prwc_buflen, new_len, KM_SLEEP);
+		if (prwc->prwc_need32) {
+			prwc->prwc_buf32 = kmem_rezalloc(prwc->prwc_buf32,
+			    prwc->prwc_buflen, new_len, KM_SLEEP);
+		}
+		prwc->prwc_buflen = new_len;
+	}
+
+	toread = MIN(prwc->prwc_buflen - prwc->prwc_curvalid,
+	    prwc->prwc_uiop->uio_resid);
+	ASSERT3U(toread, >=, needed - prwc->prwc_curvalid);
+	start = (void *)((uintptr_t)prwc->prwc_buf + prwc->prwc_curvalid);
+	if ((ret = uiomove(start, toread, UIO_WRITE, prwc->prwc_uiop)) != 0) {
+		return (ret);
+	}
+
+	prwc->prwc_curvalid += toread;
+	return (0);
 }
 
+static const proc_control_info_t *
+prwritectl_cmd_identify(const prwritectl_t *prwc,
+    const proc_control_info_t *info, size_t ninfo, size_t cmdsize)
+{
+	long cmd;
+
+	ASSERT(cmdsize == sizeof (int32_t) || cmdsize == sizeof (long));
+	if (cmdsize == 4) {
+		cmd = (long)*(int32_t *)prwc->prwc_buf;
+	} else {
+		cmd = *(long *)prwc->prwc_buf;
+	}
+
+
+	for (size_t i = 0; i < ninfo; i++) {
+		if (info[i].pcs_cmd == cmd) {
+			return (&info[i]);
+		}
+	}
+
+	return (NULL);
+}
+
 /*
  * Control operations (lots).
+ *
+ * Users can submit one or more commands to us in the uio_t. They are required
+ * to always be complete messages. The first one that fails will cause all
+ * subsequent things to fail. Processing this can be a little tricky as the
+ * actual data size that may be required is variable, not all structures are
+ * fixed sizes and some vary based on the instructing set (e.g. x86 vs.
+ * something else).
+ *
+ * The way that we handle process locking deserves some consideration. Prior to
+ * the colonization of prwritectl and the support for dynamic sizing of data,
+ * the logic would try to read in a large chunk of data and keep a process
+ * locked throughout that period and then unlock it before reading more data. As
+ * such, we mimic that logically and basically lock it before executing the
+ * first (or any subsequent) command and then only unlock it either when we're
+ * done entirely or we need to allocate memory or read from the process.
+ *
+ * This function is a common implementation for both the ILP32 and LP64 entry
+ * points as they are mostly the same except for the sizing and control function
+ * we call.
  */
 int
-prwritectl(vnode_t *vp, uio_t *uiop, cred_t *cr)
+prwritectl_common(vnode_t *vp, uio_t *uiop, cred_t *cr,
+    const proc_control_info_t *proc_info, size_t ninfo, size_t cmdsize,
+    int (*pr_controlf)(long, void *, prnode_t *, cred_t *))
 {
-#define	MY_BUFFER_SIZE \
-		100 > 1 + sizeof (arg_t) / sizeof (long) ? \
-		100 : 1 + sizeof (arg_t) / sizeof (long)
-	long buf[MY_BUFFER_SIZE];
-	long *bufp;
-	size_t resid = 0;
-	size_t size;
-	prnode_t *pnp = VTOP(vp);
-	int error;
-	int locked = 0;
+	int ret;
+	prwritectl_t prwc;
 
-	while (uiop->uio_resid) {
+	VERIFY(cmdsize == sizeof (int32_t) || cmdsize == sizeof (long));
+
+	bzero(&prwc, sizeof (prwc));
+	prwc.prwc_pnp = VTOP(vp);
+	prwc.prwc_uiop = uiop;
+	prwc.prwc_need32 = cmdsize == sizeof (int32_t);
+
+	/*
+	 * We may have multiple commands to read and want to try to minimize the
+	 * amount of reading that we do. Our callers expect us to have a
+	 * contiguous buffer for a command's actual implementation. However, we
+	 * must have at least a single long worth of data, otherwise it's not
+	 * worth continuing.
+	 */
+	while (uiop->uio_resid > 0 || prwc.prwc_curvalid > 0) {
+		const proc_control_info_t *proc_cmd;
+		void *data;
+
 		/*
-		 * Read several commands in one gulp.
+		 * Check if we have enough data to identify a command. If not,
+		 * we read as much as we can in one gulp.
 		 */
-		bufp = buf;
-		if (resid) {	/* move incomplete command to front of buffer */
-			long *tail;
+		if ((ret = prwritectl_readin(&prwc, cmdsize)) != 0) {
+			goto out;
+		}
 
-			if (resid >= sizeof (buf))
-				break;
-			tail = (long *)((char *)buf + sizeof (buf) - resid);
-			do {
-				*bufp++ = *tail++;
-			} while ((resid -= sizeof (long)) != 0);
+		/*
+		 * Identify the command and figure out how how much data we
+		 * should have read in the kernel. Some commands have a variable
+		 * length and we need to make sure the minimum is met before
+		 * asking how much there is in general. Most things know what
+		 * the minimum length is and this pcs_minf() is not implemented.
+		 * However things that are ISA-specific require us to ask that
+		 * first.
+		 *
+		 * We also must be aware that there may not actually be enough
+		 * data present in the uio_t.
+		 */
+		if ((proc_cmd = prwritectl_cmd_identify(&prwc, proc_info,
+		    ninfo, cmdsize)) == NULL) {
+			ret = EINVAL;
+			goto out;
 		}
-		resid = sizeof (buf) - ((char *)bufp - (char *)buf);
-		if (resid > uiop->uio_resid)
-			resid = uiop->uio_resid;
-		if (error = uiomove((caddr_t)bufp, resid, UIO_WRITE, uiop))
-			return (error);
-		resid += (char *)bufp - (char *)buf;
-		bufp = buf;
 
-		do {		/* loop over commands in buffer */
-			long cmd = bufp[0];
-			arg_t *argp = (arg_t *)&bufp[1];
+		size_t needed_data = cmdsize + proc_cmd->pcs_size;
+		if (proc_cmd->pcs_minf != NULL) {
+			size_t min;
 
-			size = ctlsize(cmd, resid, argp);
-			if (size == 0)	/* incomplete or invalid command */
-				break;
+			if (!proc_cmd->pcs_minf(&min)) {
+				ret = EINVAL;
+				goto out;
+			}
+
+			needed_data += min;
+		}
+
+		if (proc_cmd->pcs_sizef != NULL) {
+			size_t extra;
+
 			/*
-			 * Perform the specified control operation.
+			 * Make sure we have the minimum amount of data that
+			 * they asked us to between the static and minf
+			 * function.
 			 */
-			if (!locked) {
-				if ((error = prlock(pnp, ZNO)) != 0)
-					return (error);
-				locked = 1;
+			if ((ret = prwritectl_readin(&prwc, needed_data)) !=
+			    0) {
+				goto out;
 			}
-			if (error = pr_control(cmd, argp, pnp, cr)) {
-				if (error == -1)	/* -1 is timeout */
-					locked = 0;
-				else
-					return (error);
+
+			VERIFY3U(prwc.prwc_curvalid, >, cmdsize);
+			data = (void *)((uintptr_t)prwc.prwc_buf + cmdsize);
+			if (!proc_cmd->pcs_sizef(data, &extra)) {
+				ret = EINVAL;
+				goto out;
 			}
-			bufp = (long *)((char *)bufp + size);
-		} while ((resid -= size) != 0);
 
-		if (locked) {
-			prunlock(pnp);
-			locked = 0;
+			needed_data += extra;
 		}
+
+		/*
+		 * Now that we know how much data we're supposed to have,
+		 * finally ensure we have the total amount we need.
+		 */
+		if ((ret = prwritectl_readin(&prwc, needed_data)) != 0) {
+			goto out;
+		}
+
+		/*
+		 * /proc has traditionally assumed control writes come in
+		 * multiples of a long. This is 4 bytes for ILP32 and 8 bytes
+		 * for LP64. When calculating the required size for a structure,
+		 * it would always round that up to the next long. However, the
+		 * exact combination of circumstances changes with the
+		 * introduction of the 64-bit kernel. For 64-bit processes we
+		 * round up when the current command we're processing isn't the
+		 * last one.
+		 *
+		 * Because of our tracking structures and caching we need to
+		 * look beyond the uio_t to make this determination. In
+		 * particular, the uio_t can have a zero resid, but we may still
+		 * have additional data to read as indicated by prwc_curvalid
+		 * exceeded the current command size. In the end, we must check
+		 * both of these cases.
+		 */
+		if ((needed_data % cmdsize) != 0) {
+			if (cmdsize == sizeof (int32_t) ||
+			    prwc.prwc_curvalid > needed_data ||
+			    prwc.prwc_uiop->uio_resid > 0) {
+				needed_data = P2ROUNDUP(needed_data,
+				    cmdsize);
+				if ((ret = prwritectl_readin(&prwc,
+				    needed_data)) != 0) {
+					goto out;
+				}
+			}
+		}
+
+		if (!prwc.prwc_locked) {
+			ret = prlock(prwc.prwc_pnp, ZNO);
+			if (ret != 0) {
+				goto out;
+			}
+			prwc.prwc_locked = B_TRUE;
+		}
+
+		/*
+		 * Run our actual command. When there is an error, then the
+		 * underlying pr_control call will have unlocked the prnode_t
+		 * on our behalf. pr_control can return -1, which is a special
+		 * error indicating a timeout occurred. In such a case the node
+		 * is unlocked; however, that we are supposed to continue
+		 * processing commands regardless.
+		 *
+		 * Finally, we must deal with with one actual wrinkle. The LP64
+		 * based logic always guarantees that we have data that is
+		 * 8-byte aligned. However, the ILP32 logic is 4-byte aligned
+		 * and the rest of the /proc code assumes it can always
+		 * dereference it. If we're not aligned, we have to bcopy it to
+		 * a temporary buffer.
+		 */
+		data = (void *)((uintptr_t)prwc.prwc_buf + cmdsize);
+#ifdef	DEBUG
+		if (cmdsize == sizeof (long)) {
+			VERIFY0((uintptr_t)data % alignof (long));
+		}
+#endif
+		if (prwc.prwc_need32 && ((uintptr_t)data % alignof (long)) !=
+		    0 && needed_data > cmdsize) {
+			bcopy(data, prwc.prwc_buf32, needed_data - cmdsize);
+			data = prwc.prwc_buf32;
+		}
+		ret = pr_controlf(proc_cmd->pcs_cmd, data, prwc.prwc_pnp, cr);
+		if (ret != 0) {
+			prwc.prwc_locked = B_FALSE;
+			if (ret > 0) {
+				goto out;
+			}
+		}
+
+		/*
+		 * Finally, now that we have processed this command, we need to
+		 * move on. To make our life simple, we basically shift all the
+		 * data in our buffer over to indicate it's been consumed. While
+		 * a little wasteful, this simplifies buffer management and
+		 * guarantees that command processing uses a semi-sanitized
+		 * state. Visually, this is the following transformation:
+		 *
+		 *  0			20		prwc.prwc_curvalid
+		 *   +------------------+----------------+
+		 *   |   needed_data    | remaining_data |
+		 *   +------------------+----------------+
+		 *
+		 * In the above example we are shifting all the data over by 20,
+		 * so remaining data starts at 0. This leaves us needed_data
+		 * bytes to clean up from what was valid.
+		 */
+		if (prwc.prwc_buf32 != NULL) {
+			bzero(prwc.prwc_buf32, needed_data - cmdsize);
+		}
+
+		if (prwc.prwc_curvalid > needed_data) {
+			size_t save_size = prwc.prwc_curvalid - needed_data;
+			void *first_save = (void *)((uintptr_t)prwc.prwc_buf +
+			    needed_data);
+			memmove(prwc.prwc_buf, first_save, save_size);
+			void *first_zero = (void *)((uintptr_t)prwc.prwc_buf +
+			    save_size);
+			bzero(first_zero, needed_data);
+		} else {
+			bzero(prwc.prwc_buf, prwc.prwc_curvalid);
+		}
+		prwc.prwc_curvalid -= needed_data;
 	}
-	return (resid? EINVAL : 0);
+
+	/*
+	 * We've managed to successfully process everything. We can actually say
+	 * this was successful now.
+	 */
+	ret = 0;
+
+out:
+	if (prwc.prwc_locked) {
+		prunlock(prwc.prwc_pnp);
+		prwc.prwc_locked = B_FALSE;
+	}
+
+	if (prwc.prwc_buf != NULL) {
+		kmem_free(prwc.prwc_buf, prwc.prwc_buflen);
+	}
+
+	if (prwc.prwc_buf32 != NULL) {
+		VERIFY(prwc.prwc_need32);
+		kmem_free(prwc.prwc_buf32, prwc.prwc_buflen);
+	}
+
+	return (ret);
 }
 
 static int
-pr_control(long cmd, arg_t *argp, prnode_t *pnp, cred_t *cr)
+pr_control(long cmd, void *generic, prnode_t *pnp, cred_t *cr)
 {
 	prcommon_t *pcp;
 	proc_t *p;
 	int unlocked;
 	int error = 0;
+	arg_t *argp = generic;
 
 	if (cmd == PCNULL)
 		return (0);
@@ -422,19 +698,9 @@
 		break;
 
 	case PCSXREG:	/* set extra registers */
-#if defined(__sparc)
-		error = pr_setxregs(pnp, &argp->prxregset);
-#else
-		error = EINVAL;
-#endif
+		error = pr_setxregs(pnp, (prxregset_t *)argp);
 		break;
 
-#if defined(__sparc)
-	case PCSASRS:	/* set ancillary state registers */
-		error = pr_setasrs(pnp, argp->asrset);
-		break;
-#endif
-
 	case PCSVADDR:	/* set virtual address at which to resume */
 		error = pr_setvaddr(pnp, argp->vaddr);
 		break;
@@ -493,6 +759,13 @@
 	return (error);
 }
 
+int
+prwritectl(vnode_t *vp, uio_t *uiop, cred_t *cr)
+{
+	return (prwritectl_common(vp, uiop, cr, proc_ctl_info,
+	    ARRAY_SIZE(proc_ctl_info), sizeof (long), pr_control));
+}
+
 #ifdef _SYSCALL32_IMPL
 
 typedef union {
@@ -507,9 +780,6 @@
 	sysset_t	sysset;		/* PCSENTRY, PCSEXIT */
 	prgregset32_t	prgregset;	/* PCSREG, PCAGENT */
 	prfpregset32_t	prfpregset;	/* PCSFPREG */
-#if defined(__sparc)
-	prxregset_t	prxregset;	/* PCSXREG */
-#endif
 	prwatch32_t	prwatch;	/* PCWATCH */
 	priovec32_t	priovec;	/* PCREAD, PCWRITE */
 	prcred32_t	prcred;		/* PCSCRED */
@@ -517,213 +787,72 @@
 	int32_t		przoneid;	/* PCSZONE */
 } arg32_t;
 
-static	int	pr_control32(int32_t, arg32_t *, prnode_t *, cred_t *);
 static	int	pr_setfpregs32(prnode_t *, prfpregset32_t *);
 
-/*
- * Note that while ctlsize32() can use argp, it must do so only in a way
- * that assumes 32-bit rather than 64-bit alignment as argp is a pointer
- * to an array of 32-bit values and only 32-bit alignment is ensured.
- */
-static size_t
-ctlsize32(int32_t cmd, size_t resid, arg32_t *argp)
+static boolean_t
+prwritectl_pcscredx32_sizef(const void *datap, size_t *sizep)
 {
-	size_t size = sizeof (int32_t);
-	size_t rnd;
-	int ngrp;
+	const prcred32_t *cred = datap;
 
-	switch (cmd) {
-	case PCNULL:
-	case PCSTOP:
-	case PCDSTOP:
-	case PCWSTOP:
-	case PCCSIG:
-	case PCCFAULT:
-		break;
-	case PCSSIG:
-		size += sizeof (siginfo32_t);
-		break;
-	case PCTWSTOP:
-		size += sizeof (int32_t);
-		break;
-	case PCKILL:
-	case PCUNKILL:
-	case PCNICE:
-		size += sizeof (int32_t);
-		break;
-	case PCRUN:
-	case PCSET:
-	case PCUNSET:
-		size += sizeof (uint32_t);
-		break;
-	case PCSVADDR:
-		size += sizeof (caddr32_t);
-		break;
-	case PCSTRACE:
-	case PCSHOLD:
-		size += sizeof (sigset_t);
-		break;
-	case PCSFAULT:
-		size += sizeof (fltset_t);
-		break;
-	case PCSENTRY:
-	case PCSEXIT:
-		size += sizeof (sysset_t);
-		break;
-	case PCSREG:
-	case PCAGENT:
-		size += sizeof (prgregset32_t);
-		break;
-	case PCSFPREG:
-		size += sizeof (prfpregset32_t);
-		break;
-#if defined(__sparc)
-	case PCSXREG:
-		size += sizeof (prxregset_t);
-		break;
-#endif
-	case PCWATCH:
-		size += sizeof (prwatch32_t);
-		break;
-	case PCREAD:
-	case PCWRITE:
-		size += sizeof (priovec32_t);
-		break;
-	case PCSCRED:
-		size += sizeof (prcred32_t);
-		break;
-	case PCSCREDX:
-		/*
-		 * We cannot derefence the pr_ngroups fields if it
-		 * we don't have enough data.
-		 */
-		if (resid < size + sizeof (prcred32_t) - sizeof (gid32_t))
-			return (0);
-		ngrp = argp->prcred.pr_ngroups;
-		if (ngrp < 0 || ngrp > ngroups_max)
-			return (0);
-
-		/* The result can be smaller than sizeof (prcred32_t) */
-		size += sizeof (prcred32_t) - sizeof (gid32_t);
-		size += ngrp * sizeof (gid32_t);
-		break;
-	case PCSPRIV:
-		if (resid >= size + sizeof (prpriv_t))
-			size += priv_prgetprivsize(&argp->prpriv);
-		else
-			return (0);
-		break;
-	case PCSZONE:
-		size += sizeof (int32_t);
-		break;
-	default:
-		return (0);
+	if (cred->pr_ngroups < 0 || cred->pr_ngroups > ngroups_max) {
+		return (B_FALSE);
 	}
 
-	/* Round up to a multiple of int32_t */
-	rnd = size & (sizeof (int32_t) - 1);
-
-	if (rnd != 0)
-		size += sizeof (int32_t) - rnd;
-
-	if (size > resid)
-		return (0);
-	return (size);
+	if (cred->pr_ngroups == 0) {
+		*sizep = 0;
+	} else {
+		*sizep = (cred->pr_ngroups - 1) * sizeof (gid32_t);
+	}
+	return (B_TRUE);
 }
 
 /*
- * Control operations (lots).
+ * When dealing with ILP32 code, we are not at a point where we can assume
+ * 64-bit aligned data. Any functions that are operating here must be aware of
+ * that.
  */
-int
-prwritectl32(struct vnode *vp, struct uio *uiop, cred_t *cr)
-{
-#define	MY_BUFFER_SIZE32 \
-		100 > 1 + sizeof (arg32_t) / sizeof (int32_t) ? \
-		100 : 1 + sizeof (arg32_t) / sizeof (int32_t)
-	int32_t buf[MY_BUFFER_SIZE32];
-	int32_t *bufp;
-	arg32_t arg;
-	size_t resid = 0;
-	size_t size;
-	prnode_t *pnp = VTOP(vp);
-	int error;
-	int locked = 0;
+static const proc_control_info_t proc_ctl_info32[] = {
+	{ PCNULL, 0, NULL, NULL },
+	{ PCSTOP, 0, NULL, NULL },
+	{ PCDSTOP, 0, NULL, NULL },
+	{ PCWSTOP, 0, NULL, NULL },
+	{ PCCSIG, 0, NULL, NULL },
+	{ PCCFAULT, 0, NULL, NULL },
+	{ PCSSIG, sizeof (siginfo32_t), NULL, NULL },
+	{ PCTWSTOP, sizeof (int32_t), NULL, NULL },
+	{ PCKILL, sizeof (int32_t), NULL, NULL },
+	{ PCUNKILL, sizeof (int32_t), NULL, NULL },
+	{ PCNICE, sizeof (int32_t), NULL, NULL },
+	{ PCRUN, sizeof (uint32_t), NULL, NULL },
+	{ PCSET, sizeof (uint32_t), NULL, NULL },
+	{ PCUNSET, sizeof (uint32_t), NULL, NULL },
+	{ PCSVADDR, sizeof (caddr32_t), NULL, NULL },
+	{ PCSTRACE, sizeof (sigset_t), NULL, NULL },
+	{ PCSHOLD, sizeof (sigset_t), NULL, NULL },
+	{ PCSFAULT, sizeof (fltset_t), NULL, NULL },
+	{ PCSENTRY, sizeof (sysset_t), NULL, NULL },
+	{ PCSEXIT, sizeof (sysset_t), NULL, NULL },
+	{ PCSREG, sizeof (prgregset32_t), NULL, NULL },
+	{ PCAGENT, sizeof (prgregset32_t), NULL, NULL },
+	{ PCSFPREG, sizeof (prfpregset32_t), NULL, NULL },
+	{ PCSXREG, 0, prwriteminxreg, prwritesizexreg },
+	{ PCWATCH, sizeof (prwatch32_t), NULL },
+	{ PCREAD, sizeof (priovec32_t), NULL, NULL },
+	{ PCWRITE, sizeof (priovec32_t), NULL, NULL },
+	{ PCSCRED, sizeof (prcred32_t), NULL, NULL },
+	{ PCSCREDX, sizeof (prcred32_t), NULL, prwritectl_pcscredx32_sizef },
+	{ PCSPRIV, sizeof (prpriv_t), NULL, prwritectl_pcspriv_sizef },
+	{ PCSZONE, sizeof (long), NULL },
+};
 
-	while (uiop->uio_resid) {
-		/*
-		 * Read several commands in one gulp.
-		 */
-		bufp = buf;
-		if (resid) {	/* move incomplete command to front of buffer */
-			int32_t *tail;
-
-			if (resid >= sizeof (buf))
-				break;
-			tail = (int32_t *)((char *)buf + sizeof (buf) - resid);
-			do {
-				*bufp++ = *tail++;
-			} while ((resid -= sizeof (int32_t)) != 0);
-		}
-		resid = sizeof (buf) - ((char *)bufp - (char *)buf);
-		if (resid > uiop->uio_resid)
-			resid = uiop->uio_resid;
-		if (error = uiomove((caddr_t)bufp, resid, UIO_WRITE, uiop))
-			return (error);
-		resid += (char *)bufp - (char *)buf;
-		bufp = buf;
-
-		do {		/* loop over commands in buffer */
-			int32_t cmd = bufp[0];
-			arg32_t *argp = (arg32_t *)&bufp[1];
-
-			size = ctlsize32(cmd, resid, argp);
-			if (size == 0)	/* incomplete or invalid command */
-				break;
-			/*
-			 * Perform the specified control operation.
-			 */
-			if (!locked) {
-				if ((error = prlock(pnp, ZNO)) != 0)
-					return (error);
-				locked = 1;
-			}
-
-			/*
-			 * Since some members of the arg32_t union contain
-			 * 64-bit values (which must be 64-bit aligned), we
-			 * can't simply pass a pointer to the structure as
-			 * it may be unaligned. Note that we do pass the
-			 * potentially unaligned structure to ctlsize32()
-			 * above, but that uses it a way that makes no
-			 * assumptions about alignment.
-			 */
-			ASSERT(size - sizeof (cmd) <= sizeof (arg));
-			bcopy(argp, &arg, size - sizeof (cmd));
-
-			if (error = pr_control32(cmd, &arg, pnp, cr)) {
-				if (error == -1)	/* -1 is timeout */
-					locked = 0;
-				else
-					return (error);
-			}
-			bufp = (int32_t *)((char *)bufp + size);
-		} while ((resid -= size) != 0);
-
-		if (locked) {
-			prunlock(pnp);
-			locked = 0;
-		}
-	}
-	return (resid? EINVAL : 0);
-}
-
 static int
-pr_control32(int32_t cmd, arg32_t *argp, prnode_t *pnp, cred_t *cr)
+pr_control32(long cmd, void *generic, prnode_t *pnp, cred_t *cr)
 {
 	prcommon_t *pcp;
 	proc_t *p;
 	int unlocked;
 	int error = 0;
+	arg32_t *argp = generic;
 
 	if (cmd == PCNULL)
 		return (0);
@@ -870,14 +999,10 @@
 		break;
 
 	case PCSXREG:	/* set extra registers */
-#if defined(__sparc)
 		if (PROCESS_NOT_32BIT(p))
 			error = EOVERFLOW;
 		else
-			error = pr_setxregs(pnp, &argp->prxregset);
-#else
-		error = EINVAL;
-#endif
+			error = pr_setxregs(pnp, (prxregset_t *)argp);
 		break;
 
 	case PCSVADDR:	/* set virtual address at which to resume */
@@ -987,6 +1112,12 @@
 	return (error);
 }
 
+int
+prwritectl32(struct vnode *vp, struct uio *uiop, cred_t *cr)
+{
+	return (prwritectl_common(vp, uiop, cr, proc_ctl_info32,
+	    ARRAY_SIZE(proc_ctl_info32), sizeof (int32_t), pr_control32));
+}
 #endif	/* _SYSCALL32_IMPL */
 
 /*
@@ -1302,8 +1433,8 @@
  */
 int
 pr_wait(prcommon_t *pcp,	/* prcommon referring to process/lwp */
-	timestruc_t *ts,	/* absolute time of timeout, if any */
-	int timecheck)
+    timestruc_t *ts,		/* absolute time of timeout, if any */
+    int timecheck)
 {
 	int rval;
 
@@ -1710,11 +1841,11 @@
 }
 #endif	/* _SYSCALL32_IMPL */
 
-#if defined(__sparc)
 /* ARGSUSED */
 static int
 pr_setxregs(prnode_t *pnp, prxregset_t *prxregset)
 {
+	int error;
 	proc_t *p = pnp->pr_common->prc_proc;
 	kthread_t *t = pr_thread(pnp);	/* returns locked thread */
 
@@ -1729,34 +1860,13 @@
 
 	/* drop p_lock while touching the lwp's stack */
 	mutex_exit(&p->p_lock);
-	prsetprxregs(ttolwp(t), (caddr_t)prxregset);
+	error = prsetprxregs(ttolwp(t), prxregset);
 	mutex_enter(&p->p_lock);
 
-	return (0);
+	return (error);
 }
 
 static int
-pr_setasrs(prnode_t *pnp, asrset_t asrset)
-{
-	proc_t *p = pnp->pr_common->prc_proc;
-	kthread_t *t = pr_thread(pnp);	/* returns locked thread */
-
-	if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
-		thread_unlock(t);
-		return (EBUSY);
-	}
-	thread_unlock(t);
-
-	/* drop p_lock while touching the lwp's stack */
-	mutex_exit(&p->p_lock);
-	prsetasregs(ttolwp(t), asrset);
-	mutex_enter(&p->p_lock);
-
-	return (0);
-}
-#endif
-
-static int
 pr_setvaddr(prnode_t *pnp, caddr_t vaddr)
 {
 	proc_t *p = pnp->pr_common->prc_proc;
--- old/usr/src/uts/common/fs/proc/prdata.h	Wed Apr 12 20:33:30 2023
+++ new/usr/src/uts/common/fs/proc/prdata.h	Wed Apr 12 20:33:30 2023
@@ -29,6 +29,7 @@
 /*
  * Copyright 2019 Joyent, Inc.
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+ * Copyright 2023 Oxide Computer Company
  */
 
 #ifndef _SYS_PROC_PRDATA_H
@@ -409,11 +410,7 @@
 extern	int	prgetrvals(klwp_t *, long *, long *);
 extern	void	prgetprfpregs(klwp_t *, prfpregset_t *);
 extern	void	prsetprfpregs(klwp_t *, prfpregset_t *);
-extern	void	prgetprxregs(klwp_t *, caddr_t);
-extern	void	prsetprxregs(klwp_t *, caddr_t);
-extern	int	prgetprxregsize(proc_t *);
 extern	int	prhasfp(void);
-extern	int	prhasx(proc_t *);
 extern	caddr_t	prgetstackbase(proc_t *);
 extern	caddr_t	prgetpsaddr(proc_t *);
 extern	int	prisstep(klwp_t *);
@@ -421,6 +418,76 @@
 extern	int	prfetchinstr(klwp_t *, ulong_t *);
 extern	ushort_t prgetpctcpu(uint64_t);
 
+/*
+ * This set of routines is used by platforms to implement support for the
+ * 'xregs' or extended registers in /proc. Unlike other registers which
+ * generally have a well-defined value determined by the ABI that never changes,
+ * we expect these to change.
+ *
+ * An important thing to note is that you'll see we have moved away from a
+ * traditional version of a fixed size, non-opaque definition of the
+ * prxregset_t. This is because the size varies and we don't want applications
+ * to incorrectly bake a real definition in and cause problems where extending
+ * it becomes very hard to do (ala the prgregset_t and prfregset_t). This is a
+ * little more work for everyone implementing it, but it does ensure that we are
+ * generally in better shape.
+ *
+ * Here are the semantics of what these are required * to do and how the fit
+ * together:
+ *
+ *   o prhasx		Determine if the process in question supports the
+ *			extended register sets. Note, this is may be a
+ *			process-specific setting due to things like whether or
+ *			not the FPU is enabled or other things.
+ *
+ *   o prgetxregsize	This returns the size of the actual xregs file for a
+ *			given process. This may change between processes because
+ *			not every process may have the same set of extended
+ *			features enabled (e.g. AMX on x86).
+ *
+ *   o prwriteminxreg	This is used by the prwritectl() and related worlds to
+ *			determine the minimum amount of data that much be
+ *			present to determine if the actual size of a write is
+ *			valid. If xregs is not supported, then this should
+ *			return B_FALSE.
+ *
+ *   o prwritesizexreg	This is meant to indicate how much data is required to
+ *			be copied in for a given xregs write. The base data will
+ *			already be present from having called prwriteminxreg
+ *			previously. If xregs are not supported this should
+ *			return B_FALSE.
+ *
+ *			There is a wrinkle in this which is not true of other
+ *			callers. The data that we are given is not guaranteed to
+ *			be aligned in the slightest due to the need to support
+ *			both ILP32 and LP64!
+ *
+ *   o prgetprxregs	This is a request to fill in the xregs data. Right now
+ *			the system guarantees that the buffer size is at least
+ *			the result of the prgetprxregs() call for this process.
+ *			Callers may assume that the process remains locked
+ *			between the two so that the size doesn't change.
+ *
+ *   o prsetprxregs	This is a request to set the xregs data. The only
+ *			assumption that should be made is that the validation of
+ *			the size has been done in prvalidpcsxreg() as been
+ *			performed. Users can and will potentially try to trick
+ *			us with invalid values. Do not blindly apply this unless
+ *			there is something that is impossible about that, but
+ *			given that our recommendations for this are variable
+ *			width data, that should not happen.
+ *
+ *			If xregs are not supported this should return EINVAL.
+ *			While yes other errnos may make more sense, that is what
+ *			we have always returned in /proc for this case.
+ */
+extern	int	prhasx(proc_t *);
+extern	size_t	prgetprxregsize(proc_t *);
+extern	void	prgetprxregs(klwp_t *, prxregset_t *);
+extern	boolean_t prwriteminxreg(size_t *);
+extern	boolean_t prwritesizexreg(const void *, size_t *);
+extern	int	prsetprxregs(klwp_t *, prxregset_t *);
+
 #endif	/* _KERNEL */
 
 #ifdef	__cplusplus
--- old/usr/src/uts/common/fs/proc/prioctl.c	Wed Apr 12 20:33:30 2023
+++ new/usr/src/uts/common/fs/proc/prioctl.c	Wed Apr 12 20:33:30 2023
@@ -23,6 +23,7 @@
  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  * Copyright 2019 Joyent, Inc.
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
@@ -209,12 +210,24 @@
 		return (EBADF);
 
 	/*
+	 * The following commands are no longer supported. They were present on
+	 * SPARC and were always errors on other platforms. We now explicitly
+	 * return ENOTSUP to indicate that this is no longer done.
+	 */
+	switch (cmd) {
+	case PIOCSXREG:
+		return (ENOTSUP);
+	default:
+		break;
+	}
+
+	/*
 	 * Perform any necessary copyin() operations before
 	 * locking the process.  Helps avoid deadlocks and
 	 * improves performance.
 	 *
 	 * Also, detect invalid ioctl codes here to avoid
-	 * locking a process unnnecessarily.
+	 * locking a process unnecessarily.
 	 *
 	 * Also, prepare to allocate space that will be needed below,
 	 * case by case.
@@ -245,13 +258,8 @@
 	case PIOCMAXSIG:
 	case PIOCGXREGSIZE:
 		break;
-	case PIOCSXREG:		/* set extra registers */
 	case PIOCGXREG:		/* get extra registers */
-#if defined(__sparc)
-		thingsize = sizeof (prxregset_t);
-#else
-		thingsize = 0;
-#endif
+		thingsize = prgetprxregsize(p);
 		break;
 	case PIOCACTION:
 		thingsize = (nsig-1) * sizeof (struct sigaction);
@@ -392,17 +400,6 @@
 	case PIOCLUSAGE:
 		zdisp = ZYES;
 		break;
-	case PIOCSXREG:		/* set extra registers */
-		/*
-		 * perform copyin before grabbing the process lock
-		 */
-		if (thing) {
-			if (copyin(cmaddr, thing, thingsize)) {
-				kmem_free(thing, thingsize);
-				return (EFAULT);
-			}
-		}
-		/* fall through... */
 	default:
 		zdisp = ZNO;
 		break;
@@ -789,12 +786,19 @@
 
 	case PIOCGXREGSIZE:	/* get the size of the extra registers */
 	{
-		int xregsize;
-
 		if (prhasx(p)) {
+			size_t xregsize;
+			int abisize;
+
 			xregsize = prgetprxregsize(p);
 			prunlock(pnp);
-			if (copyout(&xregsize, cmaddr, sizeof (xregsize)))
+			if (xregsize > INT_MAX) {
+				error = EOVERFLOW;
+				break;
+			}
+
+			abisize = (int)xregsize;
+			if (copyout(&abisize, cmaddr, sizeof (abisize)))
 				error = EFAULT;
 		} else {
 			prunlock(pnp);
@@ -825,24 +829,6 @@
 		}
 		break;
 
-	case PIOCSXREG:		/* set extra registers */
-		if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t))
-			error = EBUSY;
-		else if (!prhasx(p))
-			error = EINVAL;	/* No extra register support */
-		else if (thing) {
-			/* drop p_lock while touching the lwp's stack */
-			mutex_exit(&p->p_lock);
-			prsetprxregs(lwp, thing);
-			mutex_enter(&p->p_lock);
-		}
-		prunlock(pnp);
-		if (thing) {
-			kmem_free(thing, thingsize);
-			thing = NULL;
-		}
-		break;
-
 	case PIOCSTATUS:	/* get process/lwp status */
 		oprgetstatus(t, &un.prstat, VTOZONE(vp));
 		prunlock(pnp);
@@ -1764,12 +1750,24 @@
 		return (EBADF);
 
 	/*
+	 * The following commands are no longer supported. They were present on
+	 * SPARC and were always errors on other platforms. We now explicitly
+	 * return ENOTSUP to indicate that this is no longer done.
+	 */
+	switch (cmd) {
+	case PIOCSXREG:
+		return (ENOTSUP);
+	default:
+		break;
+	}
+
+	/*
 	 * Perform any necessary copyin() operations before
 	 * locking the process.  Helps avoid deadlocks and
 	 * improves performance.
 	 *
 	 * Also, detect invalid ioctl codes here to avoid
-	 * locking a process unnnecessarily.
+	 * locking a process unnecessarily.
 	 *
 	 * Also, prepare to allocate space that will be needed below,
 	 * case by case.
@@ -1800,13 +1798,8 @@
 	case PIOCMAXSIG:
 	case PIOCGXREGSIZE:
 		break;
-	case PIOCSXREG:		/* set extra registers */
 	case PIOCGXREG:		/* get extra registers */
-#if defined(__sparc)
-		thingsize = sizeof (prxregset_t);
-#else
-		thingsize = 0;
-#endif
+		thingsize = prgetprxregsize(p);
 		break;
 	case PIOCACTION:
 		thingsize = (nsig-1) * sizeof (struct sigaction32);
@@ -1947,17 +1940,6 @@
 	case PIOCLUSAGE:
 		zdisp = ZYES;
 		break;
-	case PIOCSXREG:		/* set extra registers */
-		/*
-		 * perform copyin before grabbing the process lock
-		 */
-		if (thing) {
-			if (copyin(cmaddr, thing, thingsize)) {
-				kmem_free(thing, thingsize);
-				return (EFAULT);
-			}
-		}
-		/* fall through... */
 	default:
 		zdisp = ZNO;
 		break;
@@ -2384,12 +2366,19 @@
 
 	case PIOCGXREGSIZE:	/* get the size of the extra registers */
 	{
-		int xregsize;
-
 		if (prhasx(p)) {
+			size_t xregsize;
+			int abisize;
+
 			xregsize = prgetprxregsize(p);
 			prunlock(pnp);
-			if (copyout(&xregsize, cmaddr, sizeof (xregsize)))
+			if (xregsize > INT_MAX) {
+				error = EOVERFLOW;
+				break;
+			}
+
+			abisize = (int)xregsize;
+			if (copyout(&abisize, cmaddr, sizeof (abisize)))
 				error = EFAULT;
 		} else {
 			prunlock(pnp);
@@ -2418,26 +2407,6 @@
 			error = EFAULT;
 		if (thing) {
 			kmem_free(thing, thingsize);
-			thing = NULL;
-		}
-		break;
-
-	case PIOCSXREG:		/* set extra registers */
-		if (PROCESS_NOT_32BIT(p))
-			error = EOVERFLOW;
-		else if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t))
-			error = EBUSY;
-		else if (!prhasx(p))
-			error = EINVAL;	/* No extra register support */
-		else if (thing) {
-			/* drop p_lock while touching the lwp's stack */
-			mutex_exit(&p->p_lock);
-			prsetprxregs(lwp, thing);
-			mutex_enter(&p->p_lock);
-		}
-		prunlock(pnp);
-		if (thing) {
-			kmem_free(thing, thingsize);
 			thing = NULL;
 		}
 		break;
--- old/usr/src/uts/common/fs/proc/prvnops.c	Wed Apr 12 20:33:31 2023
+++ new/usr/src/uts/common/fs/proc/prvnops.c	Wed Apr 12 20:33:31 2023
@@ -25,7 +25,7 @@
  * Copyright (c) 2017 by Delphix. All rights reserved.
  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
  * Copyright 2022 MNX Cloud, Inc.
- * Copyright 2022 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*	Copyright (c) 1984,	 1986, 1987, 1988, 1989 AT&T	*/
@@ -1737,24 +1737,44 @@
 static int
 pr_read_xregs(prnode_t *pnp, uio_t *uiop, cred_t *cr)
 {
-#if defined(__sparc)
 	proc_t *p;
 	kthread_t *t;
 	int error;
-	char *xreg;
+	void *xreg;
 	size_t size;
 
 	ASSERT(pnp->pr_type == PR_XREGS);
 
-	xreg = kmem_zalloc(sizeof (prxregset_t), KM_SLEEP);
-
 	if ((error = prlock(pnp, ZNO)) != 0)
-		goto out;
+		return (error);
 
 	p = pnp->pr_common->prc_proc;
 	t = pnp->pr_common->prc_thread;
 
-	size = prhasx(p)? prgetprxregsize(p) : 0;
+	/*
+	 * While we would prefer to do the allocation with holding the process
+	 * this way, we can only determine this size while holding the process
+	 * as the hold guarantees us:
+	 *
+	 *  o That the process in question actualy exists.
+	 *  o That the process in question cannot change the set of FPU features
+	 *    it has enabled.
+	 *
+	 * We will drop p_lock across the allocation call itself. This should be
+	 * safe as the enabled feature set should not change while the process
+	 * is locked (e.g. enabling extending FPU state like AMX on x86 should
+	 * require the process to be locked).
+	 */
+	size = prhasx(p) ? prgetprxregsize(p) : 0;
+	if (size == 0) {
+		prunlock(pnp);
+		return (0);
+	}
+	mutex_exit(&p->p_lock);
+	xreg = kmem_zalloc(size, KM_SLEEP);
+	mutex_enter(&p->p_lock);
+	ASSERT3U(size, ==, prgetprxregsize(p));
+
 	if (uiop->uio_offset >= size) {
 		prunlock(pnp);
 		goto out;
@@ -1768,11 +1788,8 @@
 
 	error = pr_uioread(xreg, size, uiop);
 out:
-	kmem_free(xreg, sizeof (prxregset_t));
+	kmem_free(xreg, size);
 	return (error);
-#else
-	return (0);
-#endif
 }
 
 static int
--- old/usr/src/uts/common/os/kmem.c	Wed Apr 12 20:33:31 2023
+++ new/usr/src/uts/common/os/kmem.c	Wed Apr 12 20:33:31 2023
@@ -20,11 +20,10 @@
  */
 /*
  * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2017, Joyent, Inc.
  * Copyright (c) 2012, 2017 by Delphix. All rights reserved.
  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2018, Joyent, Inc.
- * Copyright 2020 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*
@@ -2820,6 +2819,32 @@
 	mutex_enter(&cp->cache_lock);
 }
 
+/*
+ * kmem_rezalloc() is currently considered private until we sort out how we want
+ * to handle realloc vs. reallocf style interfaces.
+ */
+void *
+kmem_rezalloc(void *oldbuf, size_t oldsize, size_t newsize, int kmflag)
+{
+	void *newbuf = kmem_alloc(newsize, kmflag);
+	if (newbuf == NULL) {
+		return (NULL);
+	}
+
+	bcopy(oldbuf, newbuf, MIN(oldsize, newsize));
+	if (newsize > oldsize) {
+		void *start = (void *)((uintptr_t)newbuf + oldsize);
+		bzero(start, newsize - oldsize);
+	}
+
+	if (oldbuf != NULL) {
+		ASSERT3U(oldsize, !=, 0);
+		kmem_free(oldbuf, oldsize);
+	}
+
+	return (newbuf);
+}
+
 void *
 kmem_zalloc(size_t size, int kmflag)
 {
--- old/usr/src/uts/common/os/priv.c	Wed Apr 12 20:33:31 2023
+++ new/usr/src/uts/common/os/priv.c	Wed Apr 12 20:33:31 2023
@@ -112,7 +112,7 @@
  * Guts of prgetprivsize.
  */
 int
-priv_prgetprivsize(prpriv_t *tmpl)
+priv_prgetprivsize(const prpriv_t *tmpl)
 {
 	return (sizeof (prpriv_t) +
 	    PRIV_SETBYTES - sizeof (priv_chunk_t) +
--- old/usr/src/uts/common/sys/Makefile	Wed Apr 12 20:33:31 2023
+++ new/usr/src/uts/common/sys/Makefile	Wed Apr 12 20:33:31 2023
@@ -29,7 +29,7 @@
 # Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
 # Copyright 2019 Peter Tribble.
 # Copyright 2015, Joyent, Inc. All rights reserved.
-# Copyright 2022 Oxide Computer Company
+# Copyright 2023 Oxide Computer Company
 #
 
 include $(SRC)/uts/Makefile.uts
@@ -551,6 +551,7 @@
 	stat.h			\
 	statfs.h		\
 	statvfs.h		\
+	stdalign.h		\
 	stdbool.h		\
 	stddef.h		\
 	stdint.h		\
--- old/usr/src/uts/common/sys/kmem.h	Wed Apr 12 20:33:31 2023
+++ new/usr/src/uts/common/sys/kmem.h	Wed Apr 12 20:33:31 2023
@@ -63,6 +63,7 @@
 extern void kmem_dump_init(size_t);
 extern void kmem_dump_begin(void);
 extern size_t kmem_dump_finish(char *buf, size_t size);
+extern void *kmem_rezalloc(void *, size_t, size_t, int);
 
 #endif	/* _KERNEL */
 
--- old/usr/src/uts/common/sys/priv.h	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/common/sys/priv.h	Wed Apr 12 20:33:32 2023
@@ -205,7 +205,7 @@
 struct prpriv;
 struct cred;
 
-extern int priv_prgetprivsize(struct prpriv *);
+extern int priv_prgetprivsize(const struct prpriv *);
 extern void cred2prpriv(const struct cred *, struct prpriv *);
 extern int priv_pr_spriv(struct proc *, struct prpriv *, const struct cred *);
 
--- old/usr/src/uts/common/sys/prsystm.h	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/common/sys/prsystm.h	Wed Apr 12 20:33:32 2023
@@ -56,6 +56,7 @@
 struct prpriv;
 struct prsecflags;
 struct prfdinfo;
+struct prxregset;
 
 struct seg;
 struct regs;
@@ -77,8 +78,8 @@
 extern void prgetpsinfo(proc_t *, struct psinfo *);
 extern void prgetlwpsinfo(kthread_t *, struct lwpsinfo *);
 extern void prgetprfpregs(klwp_t *, struct prfpregset *);
-extern void prgetprxregs(klwp_t *, caddr_t);
-extern int  prgetprxregsize(proc_t *);
+extern void prgetprxregs(klwp_t *, struct prxregset *);
+extern size_t prgetprxregsize(proc_t *);
 extern void prgetcred(proc_t *, struct prcred *);
 extern void prgetpriv(proc_t *, struct prpriv *);
 extern size_t prgetprivsize(void);
--- /dev/null	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/common/sys/stdalign.h	Wed Apr 12 20:33:32 2023
@@ -0,0 +1,56 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2016 Joyent, Inc.
+ * Copyright 2023 Oxide Computer Company
+ */
+
+#ifndef _SYS_STDALIGN_H
+#define	_SYS_STDALIGN_H
+
+/*
+ * ISO/IEC C11 stdalign.h. This header is meant to provide definitions for the
+ * alignas and alignof 'keywords' into the underlying compiler-understood value.
+ * In addition, there are two macros that are meant to define that this process
+ * has happened. C++11 added alignas/alignof as keywords and including this
+ * header is meant to cause us to still have the _is_defined macros, but not
+ * define this overall.
+ *
+ * Unlike other cases we don't use any symbol guards here (other than C++) and
+ * just allow the implementation to either have _Alignas and _Alignof or not
+ * have it and lead to a compiler error for the user. The main justification of
+ * this is that this header is only defined in C11 (and newer). It's not defined
+ * in other standards and just as if you include a non-standard header, in this
+ * case we don't try to stop that (same as if you included something like
+ * libdevinfo.h).
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef	__cplusplus
+
+#define	alignas	_Alignas
+/*CSTYLED*/
+#define	alignof	_Alignof
+
+#endif	/* !__cplusplus */
+
+#define	__alignas_is_defined	1
+#define	__alignof_is_defined	1
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_STDALIGN_H */
--- old/usr/src/uts/common/sys/types.h	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/common/sys/types.h	Wed Apr 12 20:33:32 2023
@@ -28,7 +28,7 @@
  *
  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2016 Joyent, Inc.
- * Copyright 2021 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #ifndef _SYS_TYPES_H
@@ -261,6 +261,16 @@
 	uint32_t	_l[4];
 } upad128_t;
 
+typedef union {
+	long double	_q[2];
+	uint32_t	_l[8];
+} upad256_t;
+
+typedef union {
+	long double	_q[4];
+	uint32_t	_l[16];
+} upad512_t;
+
 typedef	longlong_t	offset_t;
 typedef	u_longlong_t	u_offset_t;
 typedef u_longlong_t	len_t;
--- old/usr/src/uts/intel/brand/lx/lx_archdep.c	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/intel/brand/lx/lx_archdep.c	Wed Apr 12 20:33:32 2023
@@ -1449,7 +1449,8 @@
 		 * restoring it when the emulation is complete would clobber
 		 * those intentional side effects.
 		 */
-		savecontext(&uc, NULL);
+		/* XXX KEBE ASKS PLUMB UP EXTENDED?!? */
+		savecontext(&uc, NULL, 0);
 
 		if (on_fault(&lab)) {
 			goto badstack;
@@ -1617,7 +1618,8 @@
 		 * restoring it when the emulation is complete would clobber
 		 * those intentional side effects.
 		 */
-		savecontext32(&uc, NULL);
+		/* XXX KEBE ASKS PLUMB UP EXTENDED?!? */
+		savecontext32(&uc, NULL, 0);
 
 		if (on_fault(&lab)) {
 			goto badstack;
--- old/usr/src/uts/intel/fs/proc/prmachdep.c	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/intel/fs/proc/prmachdep.c	Wed Apr 12 20:33:32 2023
@@ -25,8 +25,12 @@
  */
 
 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
-/*	  All Rights Reserved  	*/
+/*	  All Rights Reserved	*/
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #include <sys/types.h>
 #include <sys/t_lock.h>
 #include <sys/param.h>
@@ -47,6 +51,7 @@
 #include <sys/signal.h>
 #include <sys/user.h>
 #include <sys/cpuvar.h>
+#include <sys/stdalign.h>
 
 #include <sys/fault.h>
 #include <sys/syscall.h>
@@ -236,43 +241,200 @@
 #endif	/* _SYSCALL32_IMPL */
 
 /*
- * Does the system support extra register state?
+ * This is a general function that the main part of /proc and the rest of the
+ * system uses to ask does a given process actually have extended state. Right
+ * now, this question is not process-specific, but rather CPU specific. We look
+ * at whether xsave has been enabled to determine that. While strictly speaking
+ * one could make the argument that all amd64 CPUs support fxsave and we could
+ * emulate something that only supports that, we don't think that makes sense.
  */
-/* ARGSUSED */
 int
 prhasx(proc_t *p)
 {
-	return (0);
+	return (fpu_xsave_enabled());
 }
 
 /*
- * Get the size of the extra registers.
+ * Return the minimum size that we need to determine the full size of a
+ * prxregset_t.
  */
-/* ARGSUSED */
-int
+boolean_t
+prwriteminxreg(size_t *sizep)
+{
+	*sizep = sizeof (prxregset_hdr_t);
+	return (B_TRUE);
+}
+
+/*
+ * This routine services both ILP32 and LP64 callers. We cannot assume anything
+ * about the alignment of argp and must bcopy things to known structures that we
+ * care about. We are guaranteed we have prxregset_hdr_t bytes because we asked
+ * for them above.
+ */
+boolean_t
+prwritesizexreg(const void *argp, size_t *sizep)
+{
+	prxregset_hdr_t hdr;
+
+	/*
+	 * While it's tempting to validate everything here, the only thing we
+	 * care about is that we understand the type and the size meets our
+	 * constraints:
+	 *
+	 *  o We actually have an item of type PR_TYPE_XSAVE, otherwise we
+	 *    don't know what this is.
+	 *  o The indicated size actually contains at least the
+	 *    prxregset_hdr_t.
+	 *  o The indicated size isn't larger than what the FPU tells us is
+	 *    allowed.
+	 *
+	 * We do not check if the reset of the structure makes semantic sense at
+	 * this point. We save all other validation for the normal set function
+	 * as that's when we'll have the rest of our data.
+	 */
+	bcopy(argp, &hdr, sizeof (hdr));
+	if (hdr.pr_type != PR_TYPE_XSAVE ||
+	    hdr.pr_size > fpu_proc_xregs_max_size() ||
+	    hdr.pr_size < sizeof (prxregset_hdr_t)) {
+		return (B_FALSE);
+	}
+
+	*sizep = hdr.pr_size - sizeof (prxregset_hdr_t);
+	return (B_TRUE);
+}
+
+/*
+ * Get the size of the extra registers. The ultimate size here depends on a
+ * combination of a few different things. Right now the xregs always have our
+ * header, the illumos-specific XCR information, the xsave information, and then
+ * otherwise this varies based on the items that the CPU supports.
+ *
+ * The ultimate size here is going to be:
+ *
+ *  o 1x prxregset_hdr_t
+ *  o n  prxregset_info_t structures
+ *  o The individual data for each one
+ */
+size_t
 prgetprxregsize(proc_t *p)
 {
-	return (0);
+	uint32_t size;
+
+	fpu_proc_xregs_info(p, NULL, &size, NULL);
+	return (size);
 }
 
 /*
  * Get extra registers.
  */
-/*ARGSUSED*/
 void
-prgetprxregs(klwp_t *lwp, caddr_t prx)
+prgetprxregs(klwp_t *lwp, prxregset_t *prx)
 {
-	/* no extra registers */
+	fpu_proc_xregs_get(lwp, prx);
 }
 
 /*
  * Set extra registers.
+ *
+ * We've been given a regset to set. Before we hand it off to the FPU, we have
+ * to go through and make sure that the different parts of this actually make
+ * sense. The kernel has guaranteed us through the functions above that we have
+ * the number of bytes that the header indicates are present. In particular we
+ * need to validate:
+ *
+ *   o The information in the header is reasonable: we have a known type, flags
+ *     and padding are zero, and there is at least one info structure.
+ *   o Each of the info structures has a valid type, size, and fits within the
+ *     data we were given.
+ *   o We do not validate or modify the actual data in the different pieces for
+ *     validity. That is considered something that the FPU does. Similarly if
+ *     something is read-only or not used, that is something that it checks.
+ *
+ * While we would like to return something other than EINVAL, the /proc APIs
+ * pretty much lead that to being the primary errno for all sorts of situations.
  */
-/*ARGSUSED*/
-void
-prsetprxregs(klwp_t *lwp, caddr_t prx)
+int
+prsetprxregs(klwp_t *lwp, prxregset_t *prx)
 {
-	/* no extra registers */
+	size_t infosz;
+	prxregset_hdr_t *hdr = (prxregset_hdr_t *)prx;
+
+	if (hdr->pr_type != PR_TYPE_XSAVE || hdr->pr_flags != 0 ||
+	    hdr->pr_pad[0] != 0 || hdr->pr_pad[1] != 0 || hdr->pr_pad[2] != 0 ||
+	    hdr->pr_pad[3] != 0 || hdr->pr_ninfo == 0) {
+		return (EINVAL);
+	}
+
+	infosz = hdr->pr_ninfo * sizeof (prxregset_info_t) +
+	    sizeof (prxregset_hdr_t);
+	if (infosz > hdr->pr_size) {
+		return (EINVAL);
+	}
+
+	for (uint32_t i = 0; i < hdr->pr_ninfo; i++) {
+		uint32_t exp_size;
+		size_t need_len, exp_align;
+		const prxregset_info_t *info = &hdr->pr_info[i];
+
+		switch (info->pri_type) {
+		case PRX_INFO_XCR:
+			exp_size = sizeof (prxregset_xcr_t);
+			exp_align = alignof (prxregset_xcr_t);
+			break;
+		case PRX_INFO_XSAVE:
+			exp_size = sizeof (prxregset_xsave_t);
+			exp_align = alignof (prxregset_xsave_t);
+			break;
+		case PRX_INFO_YMM:
+			exp_size = sizeof (prxregset_ymm_t);
+			exp_align = alignof (prxregset_ymm_t);
+			break;
+		case PRX_INFO_OPMASK:
+			exp_size = sizeof (prxregset_opmask_t);
+			exp_align = alignof (prxregset_opmask_t);
+			break;
+		case PRX_INFO_ZMM:
+			exp_size = sizeof (prxregset_zmm_t);
+			exp_align = alignof (prxregset_zmm_t);
+			break;
+		case PRX_INFO_HI_ZMM:
+			exp_size = sizeof (prxregset_hi_zmm_t);
+			exp_align = alignof (prxregset_hi_zmm_t);
+			break;
+		default:
+			return (EINVAL);
+		}
+
+		if (info->pri_flags != 0 || info->pri_size != exp_size) {
+			return (EINVAL);
+		}
+
+		if ((info->pri_offset % exp_align) != 0) {
+			return (EINVAL);
+		}
+
+		/*
+		 * No bytes of this item's entry should overlap with the
+		 * information area. If users want to overlap the actual data
+		 * information for some odd reason, we don't check that and let
+		 * them do what they want. However, the total data for this
+		 * region must actually fit. Because exp_size and pri_offset are
+		 * uint32_t's, we can sum them without overflow worries in an
+		 * LP64 environment.
+		 *
+		 * While we try to grantee alignment when writing this structure
+		 * out to userland, that is in no way a requirement and users
+		 * are allowed to start these structures wherever they want.
+		 * Hence that is not checked here.
+		 */
+		need_len = (size_t)exp_size + (size_t)info->pri_offset;
+		if (info->pri_offset < infosz ||
+		    need_len > (size_t)hdr->pr_size) {
+			return (EINVAL);
+		}
+	}
+
+	return (fpu_proc_xregs_set(lwp, prx));
 }
 
 /*
--- old/usr/src/uts/intel/os/archdep.c	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/intel/os/archdep.c	Wed Apr 12 20:33:32 2023
@@ -27,7 +27,7 @@
 /*
  * Copyright (c) 2018, Joyent, Inc.
  * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
- * Copyright 2022 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <sys/param.h>
@@ -239,48 +239,7 @@
 void
 setfpregs(klwp_t *lwp, fpregset_t *fp)
 {
-	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
-
-	if (fpu->fpu_flags & FPU_EN) {
-		if (!(fpu->fpu_flags & FPU_VALID)) {
-			/*
-			 * FPU context is still active, release the
-			 * ownership.
-			 */
-			fp_free(fpu);
-		}
-	}
-	/*
-	 * Else: if we are trying to change the FPU state of a thread which
-	 * hasn't yet initialized floating point, store the state in
-	 * the pcb and indicate that the state is valid.  When the
-	 * thread enables floating point, it will use this state instead
-	 * of the default state.
-	 */
-
-	switch (fp_save_mech) {
-	case FP_FXSAVE:
-		fpregset_to_fxsave(fp, fpu->fpu_regs.kfpu_u.kfpu_fx);
-		fpu->fpu_regs.kfpu_xstatus =
-		    fp->fp_reg_set.fpchip_state.xstatus;
-		break;
-
-	case FP_XSAVE:
-		fpregset_to_fxsave(fp,
-		    &fpu->fpu_regs.kfpu_u.kfpu_xs->xs_fxsave);
-		fpu->fpu_regs.kfpu_xstatus =
-		    fp->fp_reg_set.fpchip_state.xstatus;
-		fpu->fpu_regs.kfpu_u.kfpu_xs->xs_header.xsh_xstate_bv |=
-		    (XFEATURE_LEGACY_FP | XFEATURE_SSE);
-		break;
-	default:
-		panic("Invalid fp_save_mech");
-		/*NOTREACHED*/
-	}
-
-	fpu->fpu_regs.kfpu_status = fp->fp_reg_set.fpchip_state.status;
-	fpu->fpu_flags |= FPU_VALID;
-	PCB_SET_UPDATE_FPU(&lwp->lwp_pcb);
+	fpu_set_fpregset(lwp, fp);
 }
 
 /*
@@ -289,74 +248,8 @@
 void
 getfpregs(klwp_t *lwp, fpregset_t *fp)
 {
-	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
-
-	kpreempt_disable();
-	if (fpu->fpu_flags & FPU_EN) {
-		/*
-		 * If we have FPU hw and the thread's pcb doesn't have
-		 * a valid FPU state then get the state from the hw.
-		 */
-		if (fpu_exists && ttolwp(curthread) == lwp &&
-		    !(fpu->fpu_flags & FPU_VALID))
-			fp_save(fpu); /* get the current FPU state */
-	}
-
-	/*
-	 * There are 3 possible cases we have to be aware of here:
-	 *
-	 * 1. FPU is enabled.  FPU state is stored in the current LWP.
-	 *
-	 * 2. FPU is not enabled, and there have been no intervening /proc
-	 *    modifications.  Return initial FPU state.
-	 *
-	 * 3. FPU is not enabled, but a /proc consumer has modified FPU state.
-	 *    FPU state is stored in the current LWP.
-	 */
-	if ((fpu->fpu_flags & FPU_EN) || (fpu->fpu_flags & FPU_VALID)) {
-		/*
-		 * Cases 1 and 3.
-		 */
-		switch (fp_save_mech) {
-		case FP_FXSAVE:
-			fxsave_to_fpregset(fpu->fpu_regs.kfpu_u.kfpu_fx, fp);
-			fp->fp_reg_set.fpchip_state.xstatus =
-			    fpu->fpu_regs.kfpu_xstatus;
-			break;
-		case FP_XSAVE:
-			fxsave_to_fpregset(
-			    &fpu->fpu_regs.kfpu_u.kfpu_xs->xs_fxsave, fp);
-			fp->fp_reg_set.fpchip_state.xstatus =
-			    fpu->fpu_regs.kfpu_xstatus;
-			break;
-		default:
-			panic("Invalid fp_save_mech");
-			/*NOTREACHED*/
-		}
-		fp->fp_reg_set.fpchip_state.status = fpu->fpu_regs.kfpu_status;
-	} else {
-		/*
-		 * Case 2.
-		 */
-		switch (fp_save_mech) {
-		case FP_FXSAVE:
-		case FP_XSAVE:
-			/*
-			 * For now, we don't have any AVX specific field in ABI.
-			 * If we add any in the future, we need to initial them
-			 * as well.
-			 */
-			fxsave_to_fpregset(&sse_initial, fp);
-			fp->fp_reg_set.fpchip_state.xstatus =
-			    fpu->fpu_regs.kfpu_xstatus;
-			break;
-		default:
-			panic("Invalid fp_save_mech");
-			/*NOTREACHED*/
-		}
-		fp->fp_reg_set.fpchip_state.status = fpu->fpu_regs.kfpu_status;
-	}
-	kpreempt_enable();
+	bzero(fp, sizeof (*fp));
+	fpu_get_fpregset(lwp, fp);
 }
 
 #if defined(_SYSCALL32_IMPL)
@@ -528,6 +421,12 @@
 	dst->uc_brand_data[0] = (void *)(uintptr_t)src->uc_brand_data[0];
 	dst->uc_brand_data[1] = (void *)(uintptr_t)src->uc_brand_data[1];
 	dst->uc_brand_data[2] = (void *)(uintptr_t)src->uc_brand_data[2];
+
+	if (src->uc_flags & UC_XSAVE) {
+		dst->uc_xsave = (long)(uint32_t)src->uc_xsave;
+	} else {
+		dst->uc_xsave = 0;
+	}
 }
 
 #endif	/* _SYSCALL32_IMPL */
@@ -537,7 +436,7 @@
  * If in a system call, return the address of the syscall trap.
  */
 greg_t
-getuserpc()
+getuserpc(void)
 {
 	greg_t upc = lwptoregs(ttolwp(curthread))->r_pc;
 	uint32_t insn;
--- old/usr/src/uts/intel/os/cpuid.c	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/intel/os/cpuid.c	Wed Apr 12 20:33:32 2023
@@ -6095,7 +6095,7 @@
 }
 
 size_t
-cpuid_get_xsave_size()
+cpuid_get_xsave_size(void)
 {
 	return (MAX(cpuid_info0.cpi_xsave.xsav_max_size,
 	    sizeof (struct xsave_state)));
@@ -6102,6 +6102,49 @@
 }
 
 /*
+ * Export information about known offsets to the kernel. We only care about
+ * things we have actually enabled support for in %xcr0.
+ */
+void
+cpuid_get_xsave_info(uint64_t bit, size_t *sizep, size_t *offp)
+{
+	size_t size, off;
+
+	VERIFY3U(bit & xsave_bv_all, !=, 0);
+
+	if (sizep == NULL)
+		sizep = &size;
+	if (offp == NULL)
+		offp = &off;
+
+	switch (bit) {
+	case XFEATURE_LEGACY_FP:
+	case XFEATURE_SSE:
+		*sizep = sizeof (struct fxsave_state);
+		*offp = 0;
+		break;
+	case XFEATURE_AVX:
+		*sizep = cpuid_info0.cpi_xsave.ymm_size;
+		*offp = cpuid_info0.cpi_xsave.ymm_offset;
+		break;
+	case XFEATURE_AVX512_OPMASK:
+		*sizep = cpuid_info0.cpi_xsave.opmask_size;
+		*offp = cpuid_info0.cpi_xsave.opmask_offset;
+		break;
+	case XFEATURE_AVX512_ZMM:
+		*sizep = cpuid_info0.cpi_xsave.zmmlo_size;
+		*offp = cpuid_info0.cpi_xsave.zmmlo_offset;
+		break;
+	case XFEATURE_AVX512_HI_ZMM:
+		*sizep = cpuid_info0.cpi_xsave.zmmhi_size;
+		*offp = cpuid_info0.cpi_xsave.zmmhi_offset;
+		break;
+	default:
+		panic("asked for unsupported xsave feature: 0x%lx", bit);
+	}
+}
+
+/*
  * Return true if the CPUs on this system require 'pointer clearing' for the
  * floating point error pointer exception handling. In the past, this has been
  * true for all AMD K7 & K8 CPUs, although newer AMD CPUs have been changed to
@@ -6109,7 +6152,7 @@
  * feature bit and is reflected in the cpi_fp_amd_save member.
  */
 boolean_t
-cpuid_need_fp_excp_handling()
+cpuid_need_fp_excp_handling(void)
 {
 	return (cpuid_info0.cpi_vendor == X86_VENDOR_AMD &&
 	    cpuid_info0.cpi_fp_amd_save != 0);
--- old/usr/src/uts/intel/os/fpu.c	Wed Apr 12 20:33:33 2023
+++ new/usr/src/uts/intel/os/fpu.c	Wed Apr 12 20:33:33 2023
@@ -22,7 +22,7 @@
  * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright 2021 Joyent, Inc.
  * Copyright 2021 RackTop Systems, Inc.
- * Copyright 2022 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  */
 
 /*	Copyright (c) 1990, 1991 UNIX System Laboratories, Inc. */
@@ -63,6 +63,10 @@
 #include <sys/sysmacros.h>
 #include <sys/cmn_err.h>
 #include <sys/kfpu.h>
+#include <sys/stdbool.h>
+#include <sys/stdalign.h>
+#include <sys/procfs_isa.h>
+#include <sys/sunddi.h>
 
 /*
  * FPU Management Overview
@@ -75,7 +79,8 @@
  * While that state still exists, there is much more that is covered by the FPU.
  * Today, this includes not just traditional FPU state, but also supervisor only
  * state. The following state is currently managed and covered logically by the
- * idea of the FPU registers:
+ * idea of the FPU registers and more generally is called the Extended Processor
+ * States:
  *
  *    o Traditional x87 FPU
  *    o Vector Registers (%xmm, %ymm, %zmm)
@@ -82,10 +87,13 @@
  *    o Memory Protection Extensions (MPX) Bounds Registers
  *    o Protected Key Rights Registers (PKRU)
  *    o Processor Trace data
+ *    o Control-Flow Enforcement state
+ *    o Hardware Duty Cycle
+ *    o Hardware P-states
  *
  * The rest of this covers how the FPU is managed and controlled, how state is
  * saved and restored between threads, interactions with hypervisors, and other
- * information exported to user land through aux vectors. A lot of background
+ * information exported to userland through aux vectors. A lot of background
  * information is here to synthesize major parts of the Intel SDM, but
  * unfortunately, it is not a replacement for reading it.
  *
@@ -333,7 +341,7 @@
  *
  * To deal with some things like the agent lwp, we double check the state of the
  * FPU in sys_rtt_common() to make sure that it has been enabled before
- * returning to user land. In general, this path should be rare, but it's useful
+ * returning to userland. In general, this path should be rare, but it's useful
  * for the odd lwp here and there.
  *
  * The FPU state will remain valid most of the time. There are times that
@@ -362,8 +370,8 @@
  * employed. This meant that the FPU would be saved on a context switch and the
  * CR0.TS bit would be set. When a thread next tried to use the FPU, it would
  * then take a #NM trap, at which point we would restore the FPU from the save
- * area and return to user land. Given the frequency of use of the FPU alone by
- * libc, there's no point returning to user land just to trap again.
+ * area and return to userland. Given the frequency of use of the FPU alone by
+ * libc, there's no point returning to userland just to trap again.
  *
  * There are a few cases though where the FPU state may need to be changed for a
  * thread on its behalf. The most notable cases are in the case of processes
@@ -372,7 +380,7 @@
  * function. Whenever the FPU is saved, then the FPU_VALID flag is set on the
  * pcb. This indicates that the save state holds currently valid data. As a side
  * effect of this, CR0.TS will be set. To make sure that all of the state is
- * updated before returning to user land, in these cases, we set a flag on the
+ * updated before returning to userland, in these cases, we set a flag on the
  * PCB that says the FPU needs to be updated. This will make sure that we take
  * the slow path out of a system call to fix things up for the thread. Due to
  * the fact that this is a rather rare case, effectively setting the equivalent
@@ -388,7 +396,7 @@
  * what the value of CR0.TS should be. If it is set, then CR0.TS will be set.
  * However, because we eagerly restore, the only time that CR0.TS should be set
  * for a non-kernel thread is during operations where it will be cleared before
- * returning to user land and importantly, the only data that is in it is its
+ * returning to userland and importantly, the only data that is in it is its
  * own.
  *
  * Kernel FPU Usage
@@ -441,7 +449,7 @@
  * the kernel receives it in kernel context, we will die. Traditionally the #NM
  * (Device Not Available / No Math) exception generated by CR0.TS would have
  * caused us to restore the FPU. Now it is a fatal event regardless of whether
- * or not user land causes it.
+ * or not userland causes it.
  *
  * While there are some cases where the kernel uses the FPU, it is up to the
  * kernel to use the FPU in a way such that it cannot receive a trap or to use
@@ -473,8 +481,451 @@
  * the sizing thereof based on what the kernel supports. This is passed down in
  * a series of aux vectors SUN_AT_FPTYPE and SUN_AT_FPSIZE. This information is
  * initialized in fpu_subr.c.
+ *
+ * Signal Handling and the ucontext_t
+ * ----------------------------------
+ *
+ * One of the many gifts that signals give us is the twofold fact that when a
+ * signal occurs, the signal handler is allowed to change the CPU's state
+ * arbitrarily and when the signal handler is done executing, we must restore it
+ * back to the original state. However, the second part of this is that the
+ * signal handler is actually allowed to modify the state that the thread will
+ * return to! To create this facade, the kernel will create a full ucontext_t
+ * state, effectively calling getcontext(2) on the thread's behalf, and a
+ * pointer to that is given to the signal handler (the void * argument for the
+ * sa_sigaction function pointer in sigaction(2)). When libc is done with a
+ * signal, it will call setcontext(2) with that same ucontext_t.
+ *
+ * Now, the ucontext_t has a fixed ABI for both ILP32 and LP64 environments and
+ * it's often declared on the stack itself, with the signal handler spilling all
+ * this state to the stack. The ucontext_t machine portion was broken into the
+ * general purpose and floating point registers. In 64-bit code, the floating
+ * point registers were mostly the same as the results of the fxsave instruction
+ * (i.e. struct fxsave_state). While the 64-bit kernel still uses the equivalent
+ * starting point for information, it is transformed into a different shape to
+ * deal with the history of the 32-bit SYS V ABI.
+ *
+ * While this worked, if you're reading this, you're aware that the x86 FPU and
+ * extended register states didn't stop at the initial 16 128-bit %xmm
+ * registers. Since then we have added 256-bit %ymm, 512-bit %zmm, and the %k
+ * opmask registers. None of these fit inside the standard ucontext_t; however,
+ * they must all be preserved and restored across a signal. While the various
+ * x86 platform-specific ABIs all suggest that these registers are not preserved
+ * across a function call, receiving a signal is not a function call and must be
+ * thought of like a process receiving an interrupt. In other words, this
+ * extended state must be preserved.
+ *
+ * To facilitate this, we have extended the ucontext_t structure with an
+ * additional flag, UC_XSAVE, which indicates that the traditional padding
+ * member, uc_xsave, actually is a pointer to the extended state. While this is
+ * accessible outside of a signal handling context through the combination of
+ * ucontext_alloc(3C) and getcontext_extd(2), our design around saving this
+ * state is focused on signal handling. Signal handling spills all this state to
+ * the stack and if we cannot spill the entire state to the stack then our
+ * inability to deliver the signal results in the process being killed! While
+ * there are separate efforts to ensure that the signal stack sizing that is
+ * used for the minimum and maximum signal sizes are sufficient, we still need
+ * to do our part to minimize the likelihood here.
+ *
+ * In designing this, we make the following observations which have helped us
+ * focus our design:
+ *
+ *   o While the start of an xsave area is the traditional 512-byte fxsave XMM
+ *     region, we already have that in the fpregs. Thus there is no reason to
+ *     duplicate it. This not only saves 512 bytes of additional stack space,
+ *     but it also means we don't have to ask which of the version of it to take
+ *     if they were to differ.
+ *
+ *   o Many applications out there aren't necessarily using the extended vectors
+ *     and even when we do make libc and others take advantage of it, it will
+ *     behoove us to ensure that they are put back into their initial state
+ *     after use. This leads us to expect that in a number of cases, the actual
+ *     extended register state will be in its initial state.
+ *
+ *   o While the signal handler does allow contents to be modified, we are
+ *     starting with making the interface private and thus allowing us to excise
+ *     components that are in their initial state.
+ *
+ *   o There are similarities to what we want to create with the compressed
+ *     xsave format; however, because we don't always have support for the
+ *     compressed format, we can't just arbitrarily say let's do a compressed
+ *     save to the user stack.
+ *
+ *   o Because we are not handing this state directly to and from hardware, we
+ *     don't need to meet some of the constraints of the compressed xsave format
+ *     around wanting alignment for the initial save or additional components.
+ *
+ * All of the above lead us to our own unique format for this data. When the
+ * UC_XSAVE flag is set in the ucontext_t, the uc_xsave member points to a
+ * uc_xsave_t structure which has a magic version number, a 32-bit length of the
+ * overall structure, and the 64-bit state bit-vector to represent which
+ * components are valid. Following this 8-byte header, each component that is
+ * present in the bit vector is immediately written out in roughly ascending bit
+ * order (the order is determined based on the order of the fpu_xsave_info
+ * array).
+ *
+ * This makes the rough logic that we have here when taking a signal and writing
+ * out this state as:
+ *
+ *   1. Ensure that the FPU is saved and that the contents of the pcb save area
+ *      are valid. That is, call fp_save() if the state is not already flagged
+ *      with FPU_VALID.
+ *
+ *   2. Copy the bit-vector from the save area and remove the XFEATURE_LEGACY_FP
+ *      and XFEATURE_SSE bits as these will be placed in the xsave area.
+ *
+ *   3. Initialize the uc_xsave_t by setting our version field, initializing the
+ *      length to the length of the current structure, and then setting the
+ *      modified bit vector above.
+ *
+ *   4. Walk each remaining bit of the bit-vector. For each set bit, copy out
+ *      its extended state starting at the current length in the header and then
+ *      increase the header size by that length.
+ *
+ *   5. Finally write out the final uc_xsave_t structure.
+ *
+ * The above process is also used when someone manually calls getcontext_extd(2)
+ * to get this state. The main difference between the two is which copyout
+ * function we use. This deserves some explanation. Our main starting point for
+ * all the logic here is fpu_signal_copyout(). It takes a copyfunc that allows
+ * the signal handling context to operate with a different copyout than we
+ * normally use in say getcontext_extd(2).
+ *
+ * When we've received a signal, we're at the intersection of several different
+ * gotchas. Normal copyout (or ddi_copyout()) will trigger watchpoints. That is,
+ * the watchpoints effectively set a copyout override function (t_copyops) that
+ * we end up vectoring to rather than a normal copyout. This allows the data to
+ * be modified and for the watchpoint to fire. While this is all well and good
+ * normally, it is problematic if we are trying to handle a signal. The signal
+ * deliver logic, sendsig(), goes through and disables the watchpoint for the
+ * region of the stack that we are copying out to. However, disabling
+ * watchpoints is not sufficient, we also need to use the copyout_noerr
+ * variants.
+ *
+ * These variants also require the use of on_fault() and no_fault() for error
+ * handling. While it is tempting to try and on_fault() the entire
+ * fpu_signal_copyout() operation, that is actually fraught for a few reasons.
+ * The first is that we don't want to disable faults during the entire operation
+ * as if the kernel messes up we will treat that as a user error. That isn't
+ * theoretical and happened during development. The second and perhaps more
+ * important issue is that correctly bounding the on_fault() / no_fault() means
+ * being careful about state. For example, kernel pre-emption is often disabled
+ * during parts of these operations, but it needs to be re-enabled when we're
+ * done. This would require tracking in some volatile variable that this had
+ * been enabled and disabled and tracking that.
+ *
+ * Instead, this is why fpu_signal_copyout() takes a copy out function as an
+ * argument. When we're in signal handling context, the function will use
+ * coypout_noerr() and wrap it in the appropriate on_fault() mechanisms.
+ *
+ * RESTORING STATE
+ *
+ * Copying out our current state is the easier half of this problem. When the
+ * kernel is done with a signal it calls setcontext(2) with the ucontext_t we
+ * assembled for it as described above. setcontext(2) isn't just used for
+ * returning from signals.
+ *
+ * The process for this goes in two steps. The first step is to copy in,
+ * validate, and transform the ucontext_t UC_XSAVE that we created above into an
+ * equivalent xsave format that we can use the appropriate xrstor function on.
+ * This first phase is implemented in fpu_signal_copyin(). Once that is done, we
+ * come back through a second phase that is driven out of restorecontext() and
+ * is implemented in fpu_set_xsave().
+ *
+ * Let's start by discussing the second part of this, which is more
+ * straightforward. In particular, the second phase assumes that all of the
+ * validation and error handling has been done by the first phase. This means
+ * here, we have a buffer that is already the appropriate size
+ * (cpuid_get_xsave_size()) and all we need to do is make sure that we can
+ * replace the actual save state with the current one.
+ *
+ * The only piece of shenanigans we have to do is around the kernel provided
+ * notion of 'status' and 'xstatus', which are cached versions of the x87 and
+ * SSE exception vectors. These are part of the fpregset ABI and therefore we
+ * need to propagate them from the temporary storage that part 1 sets up in the
+ * ignored region of the fxsave data. We use that because it is not persisted by
+ * the CPU, so clobbering it is generally alright.
+ *
+ * Once that is done, we simply note that we need a PCB update to occur to
+ * refresh the FPU state before we return to userland. Given that someone has
+ * called setcontext(2), this was always going to happen because we have to
+ * update segment registers and related, so this isn't so bad. With that, let's
+ * move onto the more nuanced part (1).
+ *
+ * When we're handling a setcontext(2) we have, in userland, a data structure
+ * that should match one we serialized out, though we cannot assume that a user
+ * has not modified it either accidentally or maliciously. Our goal is to set up
+ * the appropriate xsave state that can be passed to the CPU's xrstor. The first
+ * problem we have to deal with is where do we actually put this state?
+ *
+ * While not many programs actually call setcontext(2) on their own volition,
+ * this is going to get hit every time we take a signal. The first thought was
+ * to re-use the existing thread's save area; however, that's a bit challenging
+ * for a few reasons. In particular, we would need to ensure that we don't go
+ * off-CPU for any reason, which we cannot assume with a copyin from a user
+ * address space. In particular, it is trivial for us to hit a case where the
+ * stack has been paged out for some reason, which eschews that path.
+ *
+ * Instead, whenever a thread first calls setcontext(2), generally from signal
+ * context, we will at that time allocate another entry from the 'fpsave_cachep'
+ * kmem cache, giving us a buffer of the appropriate space to handle this. Once
+ * this buffer has been allocated, we leave it assigned to the thread's pcb and
+ * only tear it down when the thread itself finally exits. We reason that a
+ * thread that takes a signal once is either going to have the process exit
+ * shortly thereafter or is much more likely to take a signal again in the
+ * future. Many daemons and other processes set things up so signals are
+ * dispatched via one location, masking signals in other thread, using
+ * sigsuspend(2), signalfd(3C), or something similar.
+ *
+ * With this buffer in hand, we begin our task of reassembling state. Note, all
+ * of this is conditional on UC_XSAVE being set in the uc_flags member of the
+ * ucontext_t. If it is not set, then we assume that there is no extended state
+ * and will use the traditional path of setting the fpregset_t into the system
+ * via setfpregs().
+ *
+ * We first will copyin and validate the uc_xsave_t. In particular, we need to
+ * make sure the version makes sense and that the xsave component bit-vector
+ * doesn't have anything unexpected and more importantly unsupported in it, and
+ * that the addresses we've been given are within the user address space. At
+ * this point we can walk through our table of implemented bits and process
+ * them.
+ *
+ * For most components in here, the processing is straightforward. We continue
+ * walking our cursor and copy data into the kernel and place it in the
+ * appropriate place in our xsave state. If a xsave state component bit-vector
+ * isn't set, then we must ensure that we have the item in the initial state,
+ * which for everything other than the x87/SSE state is the memory being zeroed.
+ *
+ * The most unique case in the copyin state is that of the x87/SSE state. You
+ * might recall that we didn't copy it out explicitly as part of the uc_xsave_t,
+ * but instead have opted to use the single definition in the fpregset_t. Thus
+ * here, we copy it out of the fpregset_t, which the kernel has helpfully
+ * already unified into the 64-bit fxsave version prior to calling us, and
+ * install that into the save area we're building up.
+ *
+ * As part of this, there are two important pieces to be aware of. The first is
+ * that because the fpregset_t has both the status and xstatus members
+ * mentioned earlier, we temporarily copy them to the software-usable ignored
+ * areas of the fxsave state so we can corral this extra state into part (2)
+ * without needing to allocate additional space. The second piece is that when
+ * we're done processing this we explicitly remove the UC_FPU flag that would
+ * tell the kernel to proceed with updating that region. The problem is that
+ * that goes directly into the pcb's save area and not to the intermediate
+ * buffer as it uses the same entry point as /proc, mainly setfpregs().
+ *
+ * We don't do much validation of the actual contents of the registers that are
+ * being set with the exception of ensuring that no reserved bits of the mxcsr
+ * are used. This is not as strict as /proc, but failure here means the process
+ * is likely going to die (returning from setcontext() in a signal handler is
+ * fatal).
+ *
+ * /proc xregs
+ * -----------
+ *
+ * Observability of the state of the extended registers is important for
+ * understanding the system. While on the surface this is similar to signal
+ * handling, it is crucially different in a number of ways:
+ *
+ *   o In signal handling, we're trying to conserve every byte of stack that we
+ *     can.
+ *   o The /proc xregs file will end up in core files, which means that we need
+ *     a way of knowing what components are present and not present in it,
+ *     because this will vary from CPU to CPU due to the addition of
+ *     architectural features. For example, some CPUs support AVX-512, but
+ *     others do not.
+ *   o The signal handling structure is private and we're not trying to have
+ *     software modify it, on the other hand, the /proc interfaces that we
+ *     support we do want software to be able to interrogate and manipulate.
+ *     These need to be something that we can introduce additional components
+ *     into and make other changes that still allow it to work.
+ *
+ * The x86 xregs format is documented in proc(5). The short form is that the
+ * prxregset_hdr_t has a number of information entries, which are of the type
+ * prxregset_info_t. Each of the information headers has a type, size, and
+ * offset which indicate where to find the additional data.
+ *
+ * Each entry is described as one of the entries in the fpu_xsave_info[]. These
+ * items either are a 1:1 correspondence with a xsave related feature (e.g.
+ * there is one entry for each of the three AVX-512 components) or it is
+ * something synthetic that we provide as additional information such as the
+ * PRX_INFO_XCR, which is a way of getting information about the system such as
+ * what is enabled in %xcr0 out there.
+ *
+ * Unlike signal handling, we are given the buffer to place everything that
+ * needs to be written out. This is partially the design of the /proc APIs. That
+ * is, we will always assemble everything into the entire buffer that /proc asks
+ * us to, and then it will use as much or as little of it as is required.
+ * Similarly, when setting things, we don't have to worry about copying in
+ * information in the same way as signal handling does, because /proc takes care
+ * of it and always hands us a full buffer. Sizing that is a little nuanced, but
+ * is all handled in prmachdep.c.
+ *
+ * When someone performs a read of the xregs and thus is asking us for the
+ * current state, there is a little bit of nuance that we need to deal with
+ * here. The first, is whether or not the FPU is enabled and the second is if
+ * the FPU is enabled, whether a given component is noted as being in its
+ * initial state. This basically gives us three possible states for a given
+ * component:
+ *
+ *   1. FPU_EN is not set and FPU_VALID is not set. This means we need to take
+ *      the illumos FPU default for an item. More on that in a moment.
+ *   2. The saved xsave state indicates that the bit for a given component is
+ *      zero -- specifically the xsh_xstate_bv member of the struct xsave_state.
+ *      In this case, we must take the CPU's default for an item. This is
+ *      usually the same as illumos, but not always.
+ *   3. The saved xsave state indicates that a given component's state bit is
+ *      valid. The simplest of our cases. We can just take what we have from the
+ *      xsave state.
+ *
+ * The CPU's default state for most components other than the x87/SSE state is
+ * to have it be zeroed. This is what we treat as our default state as well. The
+ * primary difference is in the initialization of the x87/SSE state. The SYS V
+ * ABI requires that we enable a different floating point control word then the
+ * hardware default. This means that when we're dealing with case (1) for
+ * x87/SSE we have to be more careful than the other components. Thankfully for
+ * everything else this is just keeping it zeroed.
+ *
+ * A reasonable question would be why not just skip components that aren't
+ * marked as present. There are a few reasons we take a different approach and
+ * always include it. Both of these are to make lives simpler for consumers. In
+ * the first case, when someone is performing a read and wants to reassemble and
+ * answer the question of 'what is the value of %ymm0 or %zmm15', they have
+ * to combine multiple disparate parts. If one knows that the data we put into
+ * there is always valid and represents what is in hardware and doesn't have to
+ * keep track of what are the defaults in different circumstances, then that
+ * greatly simplifies consumers lives. It also helps us for core files and other
+ * observability cases because the answer to what is the operating system's
+ * default may change over time.
+ *
+ * Similarly, including all the possible structures means that we have
+ * simplified someone who does a write. Writes are always setting the full state
+ * of a thread, meaning that if someone wants to modify only a single register
+ * they must do a read, modify, and write. By including everything that they
+ * might need, it makes it easier for consumers to do this and not have to cons
+ * up the whole structure on their own.
+ *
+ * When we're setting state, things change around a little bit. We have a few
+ * constraints that are laid out in proc(5). In particular, we require that the
+ * PRX_INFO_XSAVE component always be present to tell us which other components
+ * we expect to be here and which ones we don't. We also are much stricter about
+ * writes in several ways. Of all the components, the PRX_INFO_XCR is read-only
+ * and may not be modified by a calling process. In addition, when we have
+ * 32-bit applications which have reserved registers in the %ymm, %zmm, etc.
+ * segments, if they are being written to and have modifications, then we will
+ * indicate an error there.
+ *
+ * Because we are given the entire buffer from userland and don't need to have
+ * an intermediate place to copy it in, we will validate the entire thing in
+ * advance. Once it has been validated and we consider it legal, then we will
+ * translate each entry into its corresponding entry in pcb's normal floating
+ * point state. This is different from signal handling mostly because of the
+ * fact that we are not using copyin, and once we get to this point, there is
+ * no more validation, so we don't have the same concerns around blocking while
+ * pre-emption is disabled.
+ *
+ * The Wrinkle with fpregs
+ * -----------------------
+ *
+ * When we instead turn our attention to the fpregs, whether we're gathering
+ * them as part of the ucontext_t or as part of /proc, there are a few
+ * complications that we need to be aware of when we're operating on a kernel
+ * that is using xsave as the save mechanism. When we're using fxsave as the
+ * save mechanism, the CPU will always save the entire 512-byte fxsave region.
+ * The fpregs ABI that the kernel expects is basically this structure itself,
+ * which is transformed into a 32-bit compatible form in archdep.c.
+ *
+ * But xsave makes this much more complex and has been a source of historical
+ * bugs in the system. In particular, unlike fxsave, xsave has its component bit
+ * vector that is written out to indicate validity. This means that blindly
+ * copying the fxsave area without checking those bits will lead us to do the
+ * wrong thing. The XMM state flag mostly covers the 16 128-bit %xmm registers,
+ * while the x87 legacy fp flag covers the rest of the state. This is all good,
+ * aside from the MCXSR.
+ *
+ * One of the more complicated pieces of xsave state management is correctly
+ * answering the question of when the MXCSR is written out to xsave_state. In
+ * practice, this is rather convoluted and varies. If either the XMM or AVX
+ * feature bits are set then the CPU will write out the MXCSR and its mask
+ * register into the traditional fxsave state region. This behavior is dependent
+ * on the type of save function that we use. xsave and xsaveopt will look at the
+ * AVX feature bit; however, xsavec does not and only considers the SSE feature
+ * bit. This means that when we're retrieving things, we need to check both of
+ * those bits to determine if we should use the initial state or the value
+ * written out.
+ *
+ * When we come to someone trying to set the fpregs through /proc, the main
+ * question we have is what happens to the extended registers. We have opted to
+ * implement and document it such that a write to the fpregs only impacts the
+ * fpregs. Put differently, we will save the FPU state with fp_save() ahead of
+ * copying the data into the save area, set the state bits for x87 and XMM
+ * state, and then set the FPU to be restored. All in all, this basically means
+ * that writing to fpregs does not touch any of the %ymm, %zmm, or other state
+ * that we might have present.
+ *
+ * Forward Looking: Adding Intel AMX Support
+ * -----------------------------------------
+ *
+ * Nothing can stop the march of features being added into the FPU. One of the
+ * larger chunks that we will need to wrangle with is Intel's Advanced Matrix
+ * Extensions (AMX), which add a large chunk of xsave state to each process.
+ * While things like AVX and AVX-512 have been enabled by default, the broader
+ * OS community has not been wanting to do this for AMX ,because of the size of
+ * the state which exceeds 8 KiB. While the signal handling state went out of
+ * its way to minimize the size it wrote to the stack, if this is used, it would
+ * need to be preserved.
+ *
+ * To deal with this reality and the fact that folks don't really want to
+ * enable it by default for all purposes when its use will be quite special
+ * purpose, Intel has also added a MSR around extended feature disable or xfd.
+ * This is what we represent in the PRX_INFO_XCR prx_xfd member. Our starting
+ * assumption, and the reason that so much of the /proc and signal logic ensures
+ * that we have the thread and process around, taking as an example the unused
+ * process argument in fpu_proc_xregs_info(), is that we will follow suit and
+ * default to having support disabled, but that a process will be able to opt
+ * into it, which will result in several different assumptions around signal
+ * stack sizing and cause us to reallocate and extend the pcb's FPU save state.
+ *
+ * The following is a list of items to pay attention to for future folks who
+ * work on this:
+ *
+ *   o We will want to confirm whether other systems have opted to make this
+ *     process-wide or thread-wide. Assuming process-wide, we will need to do a
+ *     hold of all lwps while making a change. The interface for that probably
+ *     doesn't want to be /proc, as a process probably doesn't want to write to
+ *     its own control file. Changing it for another process could be done
+ *     through the agent-lwp.
+ *   o Opting into this should probably be a one-way street.
+ *   o Opting into this will need to evaluate all threads and in particular
+ *     stack sizes to confirm they adhere to the new minimum.
+ *   o We will need to make sure that setting and clearing the xfd MSR is part
+ *     of the FPU context ops and something we set by default on every CPU.
+ *   o We will need to add a new interface to allow opting into this feature.
+ *   o We will need to ensure that all subsequently created signal stacks adhere
+ *     to a required minimum size that we communicate through libc.
+ *   o We will need to make sure that both rtld and libc no longer rely on a
+ *     static value of the AT_SUN_FPSIZE, but rather realize that this can be
+ *     dynamic. At that time, we should evaluate if we can get away with not
+ *     needing to save this for rtld, even though signal handlers should assume
+ *     they will.
+ *   o The various components (because there is more than one) will want to be
+ *     added to the fpu_xsave_info[]. Consulting the processes's xfd will be
+ *     required and probably require logic changes.
+ *
+ * The above is not exhaustive. We'll probably have some other issues and fun
+ * while doing this.
  */
 
+/*
+ * The kind of FPU we advertise to rtld so it knows what to do when working
+ * through the PLT.
+ */
+int fp_elf = AT_386_FPINFO_FXSAVE;
+
+/*
+ * Mechanism to save FPU state.
+ */
+int fp_save_mech = FP_FXSAVE;
+
 kmem_cache_t *fpsave_cachep;
 
 /* Legacy fxsave layout + xsave header + ymm */
@@ -489,6 +940,13 @@
 CTASSERT(sizeof (struct xsave_state) >= AVX_XSAVE_SIZE);
 
 /*
+ * Basic architectural alignment information.
+ */
+#define	FPU_ALIGN_XMM	16
+#define	FPU_ALIGN_YMM	32
+#define	FPU_ALIGN_ZMM	64
+
+/*
  * This structure is the x86 implementation of the kernel FPU that is defined in
  * uts/common/sys/kfpu.h.
  */
@@ -550,18 +1008,6 @@
 uint32_t sse_mxcsr_mask = SSE_MXCSR_MASK_DEFAULT;
 
 /*
- * Initial kfpu state for x87 used by fpinit()
- */
-const struct fnsave_state x87_initial = {
-	FPU_CW_INIT,	/* f_fcw */
-	0,		/* __f_ign0 */
-	0,		/* f_fsw */
-	0,		/* __f_ign1 */
-	0xffff,		/* f_ftw */
-	/* rest of structure is zero */
-};
-
-/*
  * This vector is patched to xsave_ctxt() or xsaveopt_ctxt() if we discover we
  * have an XSAVE-capable chip in fpu_probe.
  */
@@ -661,7 +1107,7 @@
 
 	/*
 	 * Mark that both the parent and child need to have the FPU cleaned up
-	 * before returning to user land.
+	 * before returning to userland.
 	 */
 
 	ctxop_attach(ct, fp_ctxop_allocate(cfp));
@@ -866,6 +1312,7 @@
 	 */
 	lwp->lwp_fpu = fp->fpu_regs.kfpu_u.kfpu_generic =
 	    kmem_cache_alloc(fpsave_cachep, KM_SLEEP);
+	fp->fpu_signal = NULL;
 
 	if (fp_save_mech == FP_XSAVE) {
 		/*
@@ -888,6 +1335,11 @@
 		    fp->fpu_regs.kfpu_u.kfpu_generic);
 		lwp->lwp_fpu = fp->fpu_regs.kfpu_u.kfpu_generic = NULL;
 	}
+
+	if (fp->fpu_signal != NULL) {
+		kmem_cache_free(fpsave_cachep, fp->fpu_signal);
+		fp->fpu_signal = NULL;
+	}
 }
 
 /*
@@ -917,6 +1369,8 @@
 	bcopy(lwp->lwp_pcb.pcb_fpu.fpu_regs.kfpu_u.kfpu_generic, xp, sz);
 	/* now restore the pointer */
 	lwp->lwp_pcb.pcb_fpu.fpu_regs.kfpu_u.kfpu_generic = xp;
+	/* Ensure that we don't inherit our parent's signal state */
+	lwp->lwp_pcb.pcb_fpu.fpu_signal = NULL;
 }
 
 /*
@@ -1523,4 +1977,1315 @@
 		}
 		curthread->t_lwp->lwp_pcb.pcb_fpu.fpu_flags &= ~f;
 	}
+}
+
+/*
+ * Fill in FPU information that is required by exec.
+ */
+void
+fpu_auxv_info(int *typep, size_t *lenp)
+{
+	*typep = fp_elf;
+	switch (fp_save_mech) {
+	case FP_FXSAVE:
+		*lenp = sizeof (struct fxsave_state);
+		break;
+	case FP_XSAVE:
+		*lenp = cpuid_get_xsave_size();
+		break;
+	default:
+		*lenp = 0;
+		break;
+	}
+}
+
+/*
+ * This function exists to transform an xsave_state into an fxsave_state. The
+ * way that we have to do this is nuanced. We assume that callers have already
+ * handled FPU_EN and thus we only need to consider the xsave_state and its
+ * component vector itself. This results in the following cases that we need to
+ * consider:
+ *
+ *   o Neither the x87 / XMM state bits are set. We use the hardware default and
+ *     need to ensure to copy the xsave header.
+ *   o Both x87 / XMM state bits are set. We can copy everything.
+ *   o Only the x87 bit is set. We need to copy the x87 state but make the XMM
+ *     state be in the initial case.
+ *   o Only the XMM bit is set. The reverse of the above case.
+ *
+ * The illumos and hardware defaults in 'sse_initial' and 'avx_initial' are
+ * generally the same; however, the default floating point control word is
+ * different.
+ *
+ * Finally, we have the complication of the MXCSR and MCXSR_MASK registers.
+ * Because we are using xsave and xsaveopt in the kernel right now and not
+ * xsavec, the hardware may write out the MXCSR and MXCSR_MASK registers if the
+ * XFEATURE_AVX bit is set. Therefore if we don't have the XMM bit set but AVX
+ * is set, we must also come back and copy out the MXCSR register. Sorry, we
+ * don't make the rules.
+ */
+static void
+fpu_xsave_to_fxsave(const struct xsave_state *xsave, struct fxsave_state *fx)
+{
+	const uint64_t comps = xsave->xs_header.xsh_xstate_bv;
+
+	switch (comps & (XFEATURE_LEGACY_FP | XFEATURE_SSE)) {
+	case XFEATURE_LEGACY_FP | XFEATURE_SSE:
+		bcopy(xsave, fx, sizeof (*fx));
+		return;
+	case XFEATURE_LEGACY_FP:
+		bcopy(xsave, fx, offsetof(struct fxsave_state, fx_xmm));
+		fx->fx_mxcsr = SSE_MXCSR_INIT;
+		fx->fx_mxcsr_mask = 0;
+		break;
+	case XFEATURE_SSE:
+		bcopy(&sse_initial, fx, offsetof(struct fxsave_state,
+		    fx_mxcsr));
+
+		fx->fx_fcw = FPU_CW_INIT_HW;
+		fx->fx_mxcsr = xsave->xs_fxsave.fx_mxcsr;
+		fx->fx_mxcsr_mask = xsave->xs_fxsave.fx_mxcsr_mask;
+		bcopy(xsave->xs_fxsave.fx_xmm, fx->fx_xmm, sizeof (fx->fx_xmm));
+		break;
+	default:
+		bcopy(&sse_initial, fx, sizeof (*fx));
+		fx->fx_fcw = FPU_CW_INIT_HW;
+		break;
+	}
+
+	/*
+	 * Account for the AVX causing MXCSR to be valid.
+	 */
+	if ((xsave->xs_header.xsh_xstate_bv & XFEATURE_AVX) != 0 &&
+	    (xsave->xs_header.xsh_xstate_bv & XFEATURE_SSE) == 0) {
+		fx->fx_mxcsr = xsave->xs_fxsave.fx_mxcsr;
+		fx->fx_mxcsr_mask = xsave->xs_fxsave.fx_mxcsr_mask;
+	}
+}
+
+/*
+ * This function is designed to answer the question of are we using any xsave
+ * family of instructions in context switch and therefore we have this state.
+ * This should still remain true if we are using xsavec or xsaves in the kernel
+ * in the future.
+ */
+boolean_t
+fpu_xsave_enabled(void)
+{
+	return (fp_save_mech == FP_XSAVE);
+}
+
+/*
+ * The following structure is used to track and manage the programmatic
+ * construction of /proc and signal stack spilling of xsave information. All
+ * known xsave types that the kernel supports must be included here.
+ */
+typedef struct xsave_proc_info {
+	/*
+	 * This matches the /proc xregs type that this data represents. This s
+	 * used for /proc only.
+	 */
+	uint32_t xi_type;
+	/*
+	 * This indicates the size of the /proc data that we're operating on.
+	 * This is only used for /proc.
+	 */
+	size_t	xi_size;
+	/*
+	 * This indicates the alignment that we want to have for the member when
+	 * we're writing out. This is not used when setting data. This is only
+	 * used for /proc.
+	 */
+	size_t	xi_align;
+	/*
+	 * This indicates whether this member must always be considered or not.
+	 * This is used in both /proc and context/signal handling.
+	 */
+	bool	xi_always;
+	/*
+	 * This contains the corresponding bits in the xsave bit vector that
+	 * corresponds to this entry. This is used for both /proc and
+	 * context/signal handling.
+	 */
+	uint64_t xi_bits;
+	/*
+	 * The xi_fill function pointer is used to write out the /proc regset
+	 * data (e.g. when a user reads xregs). This is only used for the /proc
+	 * handling. The xi_valid function pointer is used instead to validate a
+	 * given set of data that we've read in, while the xi_set pointer is
+	 * used to actually transform the data in the underlying fpu save area.
+	 */
+	void	(*xi_fill)(const fpu_ctx_t *, const struct xsave_proc_info *,
+	    void *);
+	bool	(*xi_valid)(model_t, const void *);
+	void	(*xi_set)(fpu_ctx_t *, const struct xsave_proc_info *,
+	    uint64_t, const void *);
+	/*
+	 * The xi_signal_in and xi_signal_out function pointers are used for
+	 * extended context and signal handling information. They are used when
+	 * reading in data from a ucontex_t and writing it out respectively.
+	 * These are only used for context/signal handling.
+	 */
+	int	(*xi_signal_in)(const struct xsave_proc_info *,
+	    const ucontext_t *, const uc_xsave_t *, void *, uintptr_t *,
+	    const uintptr_t);
+	int	(*xi_signal_out)(const struct xsave_proc_info *, fpu_copyout_f,
+	    uc_xsave_t *, const void *fpup, uintptr_t);
+} xsave_proc_info_t;
+
+static bool
+fpu_proc_xregs_initial_state(const fpu_ctx_t *fpu, uint64_t feats)
+{
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == 0) {
+		return (B_TRUE);
+	}
+
+	return ((fpu->fpu_regs.kfpu_u.kfpu_xs->xs_header.xsh_xstate_bv &
+	    feats) == 0);
+}
+
+static void
+fpu_proc_xregs_xcr_fill(const fpu_ctx_t *fpu, const xsave_proc_info_t *info,
+    void *datap)
+{
+	prxregset_xcr_t *xcr = datap;
+
+	xcr->prx_xcr_xcr0 = xsave_bv_all;
+}
+
+/*
+ * Unlike other instruction portions, we treat the xsave header and the legacy
+ * XMM section together as both are somewhat tied at the instruction hip. Unlike
+ * the latter values, the initial state here is not quite the same.
+ */
+static void
+fpu_proc_xregs_xsave_fill(const fpu_ctx_t *fpu, const xsave_proc_info_t *info,
+    void *datap)
+{
+	prxregset_xsave_t *prxsave = datap;
+	const struct xsave_state *xsave = fpu->fpu_regs.kfpu_u.kfpu_xs;
+	size_t hdr_off;
+
+	/*
+	 * In the x87/XMM case, the no device vs. initial state is different
+	 * because the initial state case still wants us to copy the real xsave
+	 * header. It's also worth calling out that the actual illumos default
+	 * fxsave state is not the same as what Intel documents. The main
+	 * difference is in what the x87 FPU control word is. This results in
+	 * the following different cases that we need to think about:
+	 *
+	 *   o FPU_EN is not set. So we use the illumos default.
+	 */
+	if ((fpu->fpu_flags & FPU_EN) == 0) {
+		bcopy(&avx_initial, prxsave, sizeof (*prxsave));
+		return;
+	}
+
+	/*
+	 * Convert all the fxsave region while taking into account the validity
+	 * of the xsave bits. The prxregset_xsave_t structure is identical in
+	 * the first 512-bits to the prxsave structure.
+	 */
+	fpu_xsave_to_fxsave(xsave, (struct fxsave_state *)prxsave);
+
+	/*
+	 * Now that we've dealt with the x87 and XMM state, take care of the
+	 * header.
+	 */
+	hdr_off = offsetof(prxregset_xsave_t, prx_xsh_xstate_bv);
+	bcopy((const void *)((uintptr_t)xsave + hdr_off),
+	    (void *)((uintptr_t)prxsave + hdr_off),
+	    sizeof (struct xsave_header));
+}
+
+static void
+fpu_proc_xregs_std_fill(const fpu_ctx_t *fpu, const xsave_proc_info_t *info,
+    void *datap)
+{
+	if (!fpu_proc_xregs_initial_state(fpu, info->xi_bits)) {
+		size_t size, off;
+		const void *xsave_off;
+
+		cpuid_get_xsave_info(info->xi_bits, &size, &off);
+		ASSERT3U(size, ==, info->xi_size);
+		xsave_off = (void *)((uintptr_t)fpu->fpu_regs.kfpu_u.kfpu_xs +
+		    off);
+		bcopy(xsave_off, datap, info->xi_size);
+	}
+}
+
+/*
+ * Users are not allowed to actually set the xcr information this way. However,
+ * to make it easier for someone to just do a read, modify, write, of the xregs
+ * data, if it is identical, then we will accept it (and do nothing).
+ */
+static bool
+fpu_proc_xregs_xcr_valid(model_t model, const void *datap)
+{
+	const prxregset_xcr_t *xcr = datap;
+
+	return (xcr->prx_xcr_xcr0 == xsave_bv_all && xcr->prx_xcr_xfd == 0 &&
+	    xcr->prx_xcr_pad[0] == 0 && xcr->prx_xcr_pad[1] == 0);
+}
+
+/*
+ * To match traditional /proc semantics, we do not error if reserved bits of
+ * MXCSR are set, they will be masked off when writing data. We do not allow
+ * someone to indicate that they are asking for compressed xsave data, hence the
+ * check that prx_xsh_comp_bv is zero. Finally, we will check that each
+ * component that was indicated in the xstate_bv is present as another item as
+ * part of the broader validation path.
+ */
+static bool
+fpu_proc_xregs_xsave_valid(model_t model, const void *datap)
+{
+	const prxregset_xsave_t *xsave = datap;
+	uint64_t rsvd[6] = { 0 };
+
+	if (bcmp(rsvd, xsave->prx_xsh_reserved, sizeof (rsvd)) != 0 ||
+	    xsave->prx_xsh_xcomp_bv != 0) {
+		return (false);
+	}
+
+	if ((xsave->prx_xsh_xstate_bv & ~xsave_bv_all) != 0) {
+		return (false);
+	}
+
+	return (true);
+}
+
+/*
+ * The YMM, ZMM, and Hi-ZMM registers are all valid when in an LP64 environment
+ * on x86; however, when operating in ILP32, subsets are reserved. We basically
+ * require that all reserved portions are set to zero as our way to accept them.
+ */
+static bool
+fpu_proc_xregs_ymm_valid(model_t model, const void *datap)
+{
+	upad128_t ymm_zero[8];
+	const prxregset_ymm_t *ymm = datap;
+
+	if (model == DATAMODEL_LP64) {
+		return (true);
+	}
+
+	bzero(&ymm_zero, sizeof (ymm_zero));
+	return (bcmp(&ymm->prx_ymm[8], &ymm_zero, sizeof (ymm_zero)) == 0);
+}
+
+static bool
+fpu_proc_xregs_zmm_valid(model_t model, const void *datap)
+{
+	upad256_t zmm_zero[8];
+	const prxregset_zmm_t *zmm = datap;
+
+	if (model == DATAMODEL_LP64) {
+		return (true);
+	}
+
+	bzero(&zmm_zero, sizeof (zmm_zero));
+	return (bcmp(&zmm->prx_zmm[8], &zmm_zero, sizeof (zmm_zero)) == 0);
+}
+
+static bool
+fpu_proc_xregs_hi_zmm_valid(model_t model, const void *datap)
+{
+	prxregset_hi_zmm_t hi_zmm_zero;
+	const prxregset_hi_zmm_t *hi_zmm = datap;
+
+	if (model == DATAMODEL_LP64) {
+		return (true);
+	}
+
+	bzero(&hi_zmm_zero, sizeof (hi_zmm_zero));
+	return (bcmp(hi_zmm, &hi_zmm_zero, sizeof (hi_zmm_zero)) == 0);
+}
+
+/*
+ * The xsave state consists of the first 512 byes of the XMM state and then the
+ * xsave header itself. Because of the xsave header, this structure is marked
+ * with xi_always, so we must always process and consider it.
+ *
+ * Semantically if either of the bits around SSE / x87 is set, then we will copy
+ * the entire thing. This may mean that we end up copying a region that is not
+ * valid into the save area; however, that should be OK as we still have the
+ * specific bit flags that indicate what we should consider or not.
+ *
+ * There is one additional wrinkle we need to consider and honor here. The CPU
+ * will load the MXCSR values if the AVX bit is set in an xrstor regardless of
+ * anything else. So if if this is set and we do not have a valid x87/XMM bits
+ * set then we will set the MXCSR to its default state in case the processor
+ * tries to load it. For reference see:
+ *
+ *   o Intel SDM Volume 1: 13.8.1 Standard Form of XRSTOR
+ *   o AMD64 Volume 2: Section 11.5.9 MXCSR State Management
+ *
+ * Note, the behavior around this changes depending on whether using the
+ * compressed xrstor or not. We are not, but it's worth being aware of. We do
+ * not worry about MXCSR_MASK because the instructions ignore it.
+ */
+static void
+fpu_proc_xregs_xsave_set(fpu_ctx_t *fpu, const xsave_proc_info_t *info,
+    uint64_t xsave_bv, const void *datap)
+{
+	const struct xsave_state *xs = datap;
+
+	if ((xsave_bv & info->xi_bits) != 0) {
+		bcopy(&xs->xs_fxsave, &fpu->fpu_regs.kfpu_u.kfpu_xs->xs_fxsave,
+		    sizeof (struct fxsave_state));
+	} else if ((xsave_bv & XFEATURE_AVX) != 0) {
+		fpu->fpu_regs.kfpu_u.kfpu_xs->xs_fxsave.fx_mxcsr =
+		    SSE_MXCSR_INIT;
+	}
+
+	bcopy(&xs->xs_header, &fpu->fpu_regs.kfpu_u.kfpu_xs->xs_header,
+	    sizeof (struct xsave_header));
+	fpu->fpu_regs.kfpu_u.kfpu_xs->xs_fxsave.fx_mxcsr &= sse_mxcsr_mask;
+}
+
+static void
+fpu_proc_xregs_std_set(fpu_ctx_t *fpu, const xsave_proc_info_t *info,
+    uint64_t xsave_bv, const void *datap)
+{
+	size_t size, off;
+	void *xsave_off;
+
+	cpuid_get_xsave_info(info->xi_bits, &size, &off);
+	xsave_off = (void *)((uintptr_t)fpu->fpu_regs.kfpu_u.kfpu_xs +
+	    off);
+	bcopy(datap, xsave_off, size);
+}
+
+/*
+ * Dealing with XMM data is a little more annoying here. If UC_FPU is set, it
+ * also contains a copy of the XMM region. That must take priority over anything
+ * we have here. In the copyout code we do not set the XMM bits here as
+ * something to copy, therefore if they are set, we currently treat that as an
+ * error.
+ *
+ * The system has always gone through and cleaned up the reserved bits in the
+ * fxsave state when someone calls setcontext(). Therefore we need to do the
+ * same thing which is why you see the masking of the mxcsr below.
+ *
+ * Finally, there is one last wrinkle here that we need to consider. The
+ * fpregset_t has historically had two private words that are used to convey the
+ * status which cache the status/exception information. Therefore, we well...
+ * cheat. Intel has left bytes 464 (0x1d0) through 511 (0x1ff) available for us
+ * to do what we want. So we will pass this through that for the moment to help
+ * us pass this state around without too much extra allocation.
+ */
+static int
+fpu_signal_copyin_xmm(const xsave_proc_info_t *info, const ucontext_t *kuc,
+    const uc_xsave_t *ucx, void *fpup, uintptr_t *udatap,
+    const uintptr_t max_udata)
+{
+	struct xsave_state *xsave = fpup;
+
+	if ((ucx->ucx_bv & info->xi_bits) != 0) {
+		return (EINVAL);
+	}
+
+	if ((kuc->uc_flags & UC_FPU) != 0) {
+		bcopy(&kuc->uc_mcontext.fpregs, &xsave->xs_fxsave,
+		    sizeof (struct fxsave_state));
+		xsave->xs_fxsave.__fx_ign2[3]._l[0] =
+		    kuc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.status;
+		xsave->xs_fxsave.__fx_ign2[3]._l[1] =
+		    kuc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.xstatus;
+		xsave->xs_fxsave.fx_mxcsr &= sse_mxcsr_mask;
+		xsave->xs_header.xsh_xstate_bv |= info->xi_bits;
+	}
+
+	return (0);
+}
+
+static int
+fpu_signal_copyin_std(const xsave_proc_info_t *info, const ucontext_t *kuc,
+    const uc_xsave_t *ucx, void *fpup, uintptr_t *udatap,
+    const uintptr_t max_udata)
+{
+	size_t len, xsave_off;
+	void *copy_to;
+	struct xsave_state *xsave = fpup;
+
+	cpuid_get_xsave_info(info->xi_bits, &len, &xsave_off);
+	if (*udatap + len > max_udata) {
+		return (EOVERFLOW);
+	}
+
+	copy_to = (void *)((uintptr_t)fpup + xsave_off);
+	if (ddi_copyin((void *)*udatap, copy_to, len, 0) != 0) {
+		return (EFAULT);
+	}
+
+	xsave->xs_header.xsh_xstate_bv |= info->xi_bits;
+	*udatap = *udatap + len;
+
+	return (0);
+}
+
+static int
+fpu_signal_copyout_std(const xsave_proc_info_t *info, fpu_copyout_f copyfunc,
+    uc_xsave_t *ucx, const void *fpup, uintptr_t udatap)
+{
+	size_t len, xsave_off;
+	const void *copy_from;
+	void *copy_to;
+	int ret;
+
+	cpuid_get_xsave_info(info->xi_bits, &len, &xsave_off);
+	copy_from = (void *)(uintptr_t)fpup + xsave_off;
+	copy_to = (void *)(udatap + ucx->ucx_len);
+
+	ret = copyfunc(copy_from, copy_to, len);
+	if (ret != 0) {
+		return (ret);
+	}
+
+	ucx->ucx_len += len;
+	ucx->ucx_bv |= info->xi_bits;
+	return (0);
+}
+
+/*
+ * This table contains information about the extended FPU states and synthetic
+ * information we create for /proc, the ucontext_t, and signal handling. The
+ * definition of the xsave_proc_info_t describes how each member is used.
+ *
+ * In general, this table is expected to be in the order of the xsave data
+ * structure itself. Synthetic elements that we create can go anywhere and new
+ * ones should be inserted at the end. This structure is walked in order to
+ * produce the /proc and signal handling logic, so changing the order is
+ * meaningful for those and probably should not be done lightly.
+ */
+static const xsave_proc_info_t fpu_xsave_info[] = { {
+	.xi_type = PRX_INFO_XCR,
+	.xi_size = sizeof (prxregset_xcr_t),
+	.xi_align = alignof (prxregset_xcr_t),
+	.xi_always = true,
+	.xi_bits = 0,
+	.xi_fill = fpu_proc_xregs_xcr_fill,
+	.xi_valid = fpu_proc_xregs_xcr_valid
+}, {
+	/*
+	 * The XSAVE entry covers both the xsave header and the %xmm registers.
+	 * Note, there is no signal copyout information for the %xmm registers
+	 * because it is expected that that data is already in the fpregset_t.
+	 */
+	.xi_type = PRX_INFO_XSAVE,
+	.xi_size = sizeof (prxregset_xsave_t),
+	.xi_align = FPU_ALIGN_XMM,
+	.xi_always = true,
+	.xi_bits = XFEATURE_LEGACY_FP | XFEATURE_SSE,
+	.xi_fill = fpu_proc_xregs_xsave_fill,
+	.xi_set = fpu_proc_xregs_xsave_set,
+	.xi_valid = fpu_proc_xregs_xsave_valid,
+	.xi_signal_in = fpu_signal_copyin_xmm
+}, {
+	.xi_type = PRX_INFO_YMM,
+	.xi_size = sizeof (prxregset_ymm_t),
+	.xi_align = FPU_ALIGN_YMM,
+	.xi_always = false,
+	.xi_bits = XFEATURE_AVX,
+	.xi_fill = fpu_proc_xregs_std_fill,
+	.xi_set = fpu_proc_xregs_std_set,
+	.xi_signal_in = fpu_signal_copyin_std,
+	.xi_valid = fpu_proc_xregs_ymm_valid,
+	.xi_signal_out = fpu_signal_copyout_std
+}, {
+	/*
+	 * There is no /proc validation function for the mask registers because
+	 * they are the same in ILP32 / LP64 and there is nothing for us to
+	 * actually validate.
+	 */
+	.xi_type = PRX_INFO_OPMASK,
+	.xi_size = sizeof (prxregset_opmask_t),
+	.xi_align = alignof (prxregset_opmask_t),
+	.xi_always = false,
+	.xi_bits = XFEATURE_AVX512_OPMASK,
+	.xi_fill = fpu_proc_xregs_std_fill,
+	.xi_set = fpu_proc_xregs_std_set,
+	.xi_signal_in = fpu_signal_copyin_std,
+	.xi_signal_out = fpu_signal_copyout_std
+}, {
+	.xi_type = PRX_INFO_ZMM,
+	.xi_size = sizeof (prxregset_zmm_t),
+	.xi_align = FPU_ALIGN_ZMM,
+	.xi_always = false,
+	.xi_bits = XFEATURE_AVX512_ZMM,
+	.xi_fill = fpu_proc_xregs_std_fill,
+	.xi_set = fpu_proc_xregs_std_set,
+	.xi_valid = fpu_proc_xregs_zmm_valid,
+	.xi_signal_in = fpu_signal_copyin_std,
+	.xi_signal_out = fpu_signal_copyout_std
+}, {
+	.xi_type = PRX_INFO_HI_ZMM,
+	.xi_size = sizeof (prxregset_hi_zmm_t),
+	.xi_align = FPU_ALIGN_ZMM,
+	.xi_always = false,
+	.xi_bits = XFEATURE_AVX512_HI_ZMM,
+	.xi_fill = fpu_proc_xregs_std_fill,
+	.xi_set = fpu_proc_xregs_std_set,
+	.xi_valid = fpu_proc_xregs_hi_zmm_valid,
+	.xi_signal_in = fpu_signal_copyin_std,
+	.xi_signal_out = fpu_signal_copyout_std
+} };
+
+static bool
+fpu_proc_xregs_include(const xsave_proc_info_t *infop)
+{
+	return (infop->xi_always || (xsave_bv_all & infop->xi_bits) != 0);
+}
+
+void
+fpu_proc_xregs_info(struct proc *p __unused, uint32_t *ninfop, uint32_t *sizep,
+    uint32_t *dstart)
+{
+	size_t ret = sizeof (prxregset_hdr_t);
+	uint32_t ninfo = 0;
+
+	ASSERT(fpu_xsave_enabled());
+
+	/*
+	 * Right now the set of flags that are enabled in the FPU is global.
+	 * That is, while the pcb's fcpu_ctx_t has the fpu_xsave_mask, the
+	 * actual things that might show up and we care about are all about what
+	 * is set up in %xcr0 which is stored in the global xsave_bv_all. If we
+	 * move to per-process FPU enablement which is likely to come with AMX,
+	 * then this will need the proc_t to look at, hence why we've set things
+	 * up with the unused variable above.
+	 *
+	 * We take two passes through the array. The first is just to count up
+	 * how many informational entries we need.
+	 */
+	for (size_t i = 0; i < ARRAY_SIZE(fpu_xsave_info); i++) {
+		if (!fpu_proc_xregs_include(&fpu_xsave_info[i]))
+			continue;
+		ninfo++;
+	}
+
+	ASSERT3U(ninfo, >, 0);
+	ret += sizeof (prxregset_info_t) * ninfo;
+
+	for (size_t i = 0; i < ARRAY_SIZE(fpu_xsave_info); i++) {
+		size_t curphase;
+		if (!fpu_proc_xregs_include(&fpu_xsave_info[i]))
+			continue;
+
+		curphase = ret % fpu_xsave_info[i].xi_align;
+		if (ret < fpu_xsave_info[i].xi_align) {
+			ret = fpu_xsave_info[i].xi_align;
+		} else if (curphase != 0) {
+			ret += curphase;
+		}
+
+		if (i == 0 && dstart != NULL) {
+			*dstart = ret;
+		}
+
+		ret += fpu_xsave_info[i].xi_size;
+	}
+
+	VERIFY3U(ret, <=, UINT32_MAX);
+	if (sizep != NULL) {
+		*sizep = ret;
+	}
+
+	if (ninfop != NULL) {
+		*ninfop = ninfo;
+	}
+}
+
+/*
+ * This function supports /proc. Because /proc does not have a process locked
+ * while processing a PCSXREG, so this tries to establish an upper bound that we
+ * will validate later in fpu_proc_xregs_set(). We basically say that if you
+ * take the maximum xsave size and add 1 KiB that is a good enough approximation
+ * for the maximum size.
+ */
+size_t
+fpu_proc_xregs_max_size(void)
+{
+	VERIFY(fpu_xsave_enabled());
+	return (cpuid_get_xsave_size() + 0x1000);
+}
+
+/*
+ * This functions supports /proc. In particular, it's meant to perform the
+ * following:
+ *
+ *  o Potentially save the current thread's registers.
+ *  o Write out the x86 xsave /proc xregs format data from the xsave data we
+ *    actually have. Note, this can be a little weird for cases where the FPU is
+ *    not actually enabled, which happens for system processes.
+ *    /proc let us read this state?
+ */
+void
+fpu_proc_xregs_get(struct _klwp *lwp, void *buf)
+{
+	uint32_t size, ninfo, curinfo, dstart;
+	fpu_ctx_t *fpu = &lwp->lwp_pcb.pcb_fpu;
+	prxregset_hdr_t *hdr = buf;
+
+	ASSERT(fpu_xsave_enabled());
+	fpu_proc_xregs_info(lwp->lwp_procp, &ninfo, &size, &dstart);
+
+	/*
+	 * Before we get going, defensively zero out all the data buffer so that
+	 * the rest of the fill functions can assume a specific base.
+	 */
+	bzero(buf, size);
+
+	kpreempt_disable();
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == FPU_EN) {
+		/*
+		 * This case suggests that thread in question doesn't have a
+		 * valid FPU save state which should only happen when it is on
+		 * CPU. If this is the case, we must ensure that we save the
+		 * current FPU state before proceeding. We also sanity check
+		 * several things here before doing this as using /proc on
+		 * yourself is always exciting. fp_save() will ensure that the
+		 * thread is flagged to go back to being an eager FPU before
+		 * returning back to userland.
+		 */
+		VERIFY3P(curthread, ==, lwptot(lwp));
+		VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+		fp_save(fpu);
+	}
+	kpreempt_enable();
+
+	hdr->pr_type = PR_TYPE_XSAVE;
+	hdr->pr_size = size;
+	hdr->pr_flags = hdr->pr_pad[0] = hdr->pr_pad[1] = hdr->pr_pad[2] =
+	    hdr->pr_pad[3] = 0;
+	hdr->pr_ninfo = ninfo;
+
+	curinfo = 0;
+	for (size_t i = 0; i < ARRAY_SIZE(fpu_xsave_info); i++) {
+		void *startp;
+		uint32_t phase;
+
+		if (!fpu_proc_xregs_include(&fpu_xsave_info[i]))
+			continue;
+
+		phase = dstart % fpu_xsave_info[i].xi_align;
+		if (dstart < fpu_xsave_info[i].xi_align) {
+			ASSERT3U(i, !=, 0);
+			dstart = fpu_xsave_info[i].xi_align;
+		} else if (phase != 0) {
+			ASSERT3U(i, !=, 0);
+			dstart += phase;
+		}
+
+		hdr->pr_info[curinfo].pri_type = fpu_xsave_info[i].xi_type;
+		hdr->pr_info[curinfo].pri_flags = 0;
+		hdr->pr_info[curinfo].pri_size = fpu_xsave_info[i].xi_size;
+		hdr->pr_info[curinfo].pri_offset = dstart;
+
+		startp = (void *)((uintptr_t)buf + dstart);
+		fpu_xsave_info[i].xi_fill(fpu, &fpu_xsave_info[i], startp);
+		dstart += fpu_xsave_info[i].xi_size;
+		ASSERT3U(curinfo, <=, ninfo);
+		curinfo++;
+	}
+}
+
+/*
+ * We have been asked to set the data in the FPU for a given thread. Our
+ * prmachdep code has already validated that the raw semantics of the data that
+ * we have are valid (that is the appropriate sizes, offsets, and flags). We now
+ * apply additional checking here:
+ *
+ *   o The xsave structure is present and only valid bits are set.
+ *   o If the xsave component bit-vector is set, we have the corresponding proc
+ *     info item.
+ *   o Read-only items are ignored if and only if they actually match what we
+ *     gave the user mostly as a courtesy to simplify things here.
+ *   o ILP32 processes which can't support many of the regions are allowed to
+ *     have the items here (as we likely gave them to them), but they must be
+ *     zero if they are set.
+ *
+ * We take a first pass through all the data, validating it makes sense for the
+ * FPU. Only after that point do we ensure that we have the FPU data in question
+ * and then we clobber all the FPU data. Part of the semantics of setting this
+ * is that we're setting the entire extended FPU.
+ */
+int
+fpu_proc_xregs_set(struct _klwp *lwp, void *buf)
+{
+	prxregset_hdr_t *prx = buf;
+	model_t model = lwp_getdatamodel(lwp);
+	uint64_t bv_found = 0;
+	const prxregset_xsave_t *xsave = NULL;
+	fpu_ctx_t *fpu = &lwp->lwp_pcb.pcb_fpu;
+
+	VERIFY(fpu_xsave_enabled());
+
+	/*
+	 * First, walk each note info header that we have from the user and
+	 * proceed to validate it. The prmachdep code has already validated that
+	 * the size, type, and offset information is valid, but it has not
+	 * validated the semantic contents of this or if someone is trying to
+	 * write something they shouldn't.
+	 *
+	 * While we walk this, we keep track of where the xsave header is. We
+	 * also track all of the bits that we have found along the way so we can
+	 * match up and ensure that everything that was set has a corresponding
+	 * bit in the xsave bitmap. If we have something in the xsave bitmap,
+	 * but not its corresponding data, then that is an error. However, we
+	 * allow folks to write data regions without the bit set in the xsave
+	 * data to make the read, modify, write process simpler.
+	 */
+	for (uint32_t i = 0; i < prx->pr_ninfo; i++) {
+		const prxregset_info_t *info = &prx->pr_info[i];
+		bool found = false;
+
+		for (size_t pt = 0; pt < ARRAY_SIZE(fpu_xsave_info); pt++) {
+			void *data;
+			if (info->pri_type != fpu_xsave_info[pt].xi_type)
+				continue;
+
+			found = true;
+			data = (void *)((uintptr_t)buf + info->pri_offset);
+			if (fpu_xsave_info[pt].xi_valid != NULL &&
+			    !fpu_xsave_info[pt].xi_valid(model, data)) {
+				return (EINVAL);
+			}
+
+			if (info->pri_type == PRX_INFO_XSAVE) {
+				xsave = data;
+			}
+			bv_found |= fpu_xsave_info[pt].xi_bits;
+			break;
+		}
+
+		if (!found) {
+			return (EINVAL);
+		}
+	}
+
+	/*
+	 * No xsave data, no dice.
+	 */
+	if (xsave == NULL) {
+		return (EINVAL);
+	}
+
+	/*
+	 * If anything is set in the xsave header that was not found as we
+	 * walked structures, then that is an error. The opposite is not true as
+	 * discussed above.
+	 */
+	if ((xsave->prx_xsh_xstate_bv & ~bv_found) != 0) {
+		return (EINVAL);
+	}
+
+	/*
+	 * At this point, we consider all the data actually valid. Now we must
+	 * set up this information in the save area. If this is our own lwp, we
+	 * must disable it first. Otherwise, we expect that it is already valid.
+	 * To try to sanitize this, we will defensively zero the entire region
+	 * as we are setting everything that will result in here.
+	 */
+	kpreempt_disable();
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == FPU_EN) {
+		/*
+		 * This case suggests that thread in question doesn't have a
+		 * valid FPU save state which should only happen when it is on
+		 * CPU. If this is the case, we explicitly disable the FPU, but
+		 * do not save it before proceeding. We also sanity check
+		 * several things here before doing this as using /proc on
+		 * yourself is always exciting. Unlike fp_save(), fp_free() does
+		 * not signal that an update is required, so we unconditionally
+		 * set that for all threads.
+		 */
+		VERIFY3P(curthread, ==, lwptot(lwp));
+		VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+		fp_free(fpu);
+	}
+	PCB_SET_UPDATE_FPU(&lwp->lwp_pcb);
+	bzero(lwp->lwp_pcb.pcb_fpu.fpu_regs.kfpu_u.kfpu_generic,
+	    cpuid_get_xsave_size());
+
+	for (uint32_t i = 0; i < prx->pr_ninfo; i++) {
+		const prxregset_info_t *info = &prx->pr_info[i];
+		bool found = false;
+
+		for (size_t pt = 0; pt < ARRAY_SIZE(fpu_xsave_info); pt++) {
+			const void *data;
+			if (info->pri_type != fpu_xsave_info[pt].xi_type)
+				continue;
+
+			/*
+			 * Check if we have a set function and if we should
+			 * include this. We may not if this is something like
+			 * PRX_INFO_XCR which is read-only.
+			 *
+			 * We may not include a given entry as it may not have
+			 * been set in the actual xsave state that we have been
+			 * asked to restore, in which case to not break the
+			 * xsaveopt logic, we must leave it in its initial
+			 * state, e.g. zeroed (generally). XMM data initial
+			 * state is not zeroed, but is marked with xi_always to
+			 * help account for this.
+			 */
+			found = true;
+			if (fpu_xsave_info[pt].xi_set == NULL)
+				break;
+			if (!fpu_xsave_info[pt].xi_always &&
+			    (xsave->prx_xsh_xstate_bv &
+			    fpu_xsave_info[pt].xi_bits) !=
+			    fpu_xsave_info[pt].xi_bits) {
+				break;
+			}
+
+			data = (void *)((uintptr_t)buf + info->pri_offset);
+			fpu_xsave_info[pt].xi_set(fpu, &fpu_xsave_info[pt],
+			    xsave->prx_xsh_xstate_bv, data);
+		}
+
+		VERIFY(found);
+	}
+	kpreempt_enable();
+
+	return (0);
+}
+
+/*
+ * To be included in the signal copyout logic we must have a copy function and
+ * the bit in question must be included. Note, we don't consult xi_always here
+ * as that is really part of what is always present for xsave logic and
+ * therefore isn't really pertinent here because of our custom format. See the
+ * big theory statement for more info.
+ */
+static bool
+fpu_signal_include(const xsave_proc_info_t *infop, uint64_t xs_bv)
+{
+	return ((infop->xi_bits & xs_bv) == infop->xi_bits &&
+	    infop->xi_signal_out != NULL);
+}
+
+/*
+ * We need to fill out the xsave related data into the ucontext_t that we've
+ * been given. We should have a valid user pointer at this point in the uc_xsave
+ * member. This is much simpler than the copyin that we have. Here are the
+ * current assumptions:
+ *
+ *   o This is being called for the current thread. This is not meant to operate
+ *     on an arbitrary thread's state.
+ *   o We cannot assume whether the FPU is valid in the pcb or not. While most
+ *     callers will have just called getfpregs() which saved the state, don't
+ *     assume that.
+ *   o We assume that the user address has the requisite required space for this
+ *     to be copied out.
+ *   o We assume that copyfunc() will ensure we are not copying into a kernel
+ *     address.
+ *
+ * For more information on the format of the data, see the 'Signal Handling and
+ * the ucontext_t' portion of the big theory statement. We copy out all the
+ * constituent parts and then come back and write out the actual final header
+ * information.
+ */
+int
+fpu_signal_copyout(struct _klwp *lwp, uintptr_t uaddr, fpu_copyout_f copyfunc)
+{
+	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
+	uint64_t xs_bv;
+	uc_xsave_t ucx;
+	int ret;
+
+	VERIFY3P(curthread, ==, lwptot(lwp));
+	VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+	ASSERT3U(fpu->fpu_flags & FPU_EN, ==, FPU_EN);
+
+	if (!fpu_xsave_enabled()) {
+		return (ENOTSUP);
+	}
+
+	/*
+	 * Unlike when we're dealing with /proc, we can unconditionally call
+	 * fp_save() because this is always called in the context that the lwp
+	 * we're operating on is always the one on CPU (which is what fp_save()
+	 * asserts).
+	 */
+	fp_save(fpu);
+
+	bzero(&ucx, sizeof (ucx));
+	ucx.ucx_vers = UC_XSAVE_VERS;
+	ucx.ucx_len += sizeof (uc_xsave_t);
+
+	xs_bv = fpu->fpu_regs.kfpu_u.kfpu_xs->xs_header.xsh_xstate_bv;
+	for (size_t i = 0; i < ARRAY_SIZE(fpu_xsave_info); i++) {
+		const xsave_proc_info_t *info = &fpu_xsave_info[i];
+
+		if (!fpu_signal_include(&fpu_xsave_info[i], xs_bv))
+			continue;
+		ret = info->xi_signal_out(info, copyfunc, &ucx,
+		    lwp->lwp_pcb.pcb_fpu.fpu_regs.kfpu_u.kfpu_generic,
+		    uaddr);
+		if (ret != 0) {
+			kpreempt_enable();
+			return (ret);
+		}
+	}
+
+	/*
+	 * Now that everything has been copied out, we should have an accurate
+	 * value in the uc_xsave_t header and we can copy that out at the start
+	 * of the user data.
+	 */
+	ret = copyfunc(&ucx, (void *)uaddr, sizeof (ucx));
+	return (ret);
+}
+
+/*
+ * Here we've been given a ucontext_t which potentially has a user pointer to
+ * xsave state that we've copied out previously. In this case we need to do the
+ * following, assuming UC_XSAVE is present:
+ *
+ *   o Copy in our header and validate it.
+ *   o Allocate an fpu context to use as a holding ground for all this data.
+ *   o If UC_FPU is set, override the xsave structure with the saved XMM state,
+ *     clear UC_FPU, and make sure that the correct xsave_bv bits are set.
+ *
+ * Currently we always allocate the additional state as a holding ground for the
+ * FPU. What we're copying in may not be valid and we don't want to clobber the
+ * existing FPU state or deal with merging it until we believe it's reasonable
+ * enough. The proc_t is here to set us up for when we have per-process settings
+ * in the extended feature disable MSRs.
+ */
+int
+fpu_signal_copyin(struct _klwp *lwp, ucontext_t *kuc)
+{
+	uc_xsave_t ucx;
+	uint64_t bv;
+	uintptr_t data, max_data;
+	void *fpu;
+	proc_t *p = lwp->lwp_procp;
+	size_t ksize;
+
+	/*
+	 * Because this has been opaque filler and the kernel has never
+	 * historically looked at it, we don't really care about the uc_xsave
+	 * pointer being garbage in the case that the flag is not set. While
+	 * this isn't perhaps the most sporting choice in some cases, this is on
+	 * the other hand, pragmatic.
+	 */
+	if ((kuc->uc_flags & UC_XSAVE) != 0) {
+		if (kuc->uc_xsave == 0) {
+			return (EINVAL);
+		}
+
+		if (!fpu_xsave_enabled()) {
+			return (ENOTSUP);
+		}
+	} else {
+		return (0);
+	}
+
+	if (ddi_copyin((const void *)kuc->uc_xsave, &ucx, sizeof (ucx), 0) !=
+	    0) {
+		return (EFAULT);
+	}
+
+	ksize = cpuid_get_xsave_size();
+	if (ucx.ucx_vers != UC_XSAVE_VERS || ucx.ucx_len < sizeof (ucx) ||
+	    ucx.ucx_len > ksize ||
+	    (ucx.ucx_bv & ~xsave_bv_all) != 0 ||
+	    (uintptr_t)p->p_as->a_userlimit - ucx.ucx_len <
+	    (uintptr_t)kuc->uc_xsave) {
+		return (EINVAL);
+	}
+
+	/*
+	 * OK, our goal right now is to recreate a valid xsave_state structure
+	 * that we'll ultimately end up having to merge with our existing one in
+	 * the FPU save state. The reason we describe this as a merge is to help
+	 * future us when we want to retain supervisor state which will never be
+	 * part of userland signal state. The design of the userland signal
+	 * state is basically to compress it as much as we can. This is done for
+	 * two reasons:
+	 *
+	 *   1) We currently consider this a private interface.
+	 *   2) We really want to minimize the actual amount of stack space we
+	 *	use as much as possible. Most applications aren't using AVX-512
+	 *	right now, so doing our own compression style is worthwhile. If
+	 *	libc adopts AVX-512 routines, we may want to change this.
+	 *
+	 * On the allocation below, our assumption is that if a thread has taken
+	 * a signal, then it is likely to take a signal again in the future (or
+	 * be shortly headed to its demise). As such, when that happens we will
+	 * leave the allocated signal stack around for the process. Most
+	 * applications don't allow all threads to take signals, so this should
+	 * hopefully help amortize the cost of the allocation.
+	 */
+	max_data = (uintptr_t)kuc->uc_xsave + ucx.ucx_len;
+	data = (uintptr_t)kuc->uc_xsave + sizeof (ucx);
+	bv = ucx.ucx_bv;
+	if (lwp->lwp_pcb.pcb_fpu.fpu_signal == NULL) {
+		lwp->lwp_pcb.pcb_fpu.fpu_signal =
+		    kmem_cache_alloc(fpsave_cachep, KM_SLEEP);
+	}
+	fpu = lwp->lwp_pcb.pcb_fpu.fpu_signal;
+
+	/*
+	 * Unconditionally initialize the memory we get in here to ensure that
+	 * it is in a reasonable state for ourselves. This ensures that unused
+	 * regions are mostly left in their initial state (the main exception
+	 * here is the x87/XMM state, but that should be OK). We don't fill in
+	 * the initial xsave state as we expect that to happen as part of our
+	 * processing.
+	 */
+	bzero(fpu, ksize);
+
+	for (size_t i = 0; i < ARRAY_SIZE(fpu_xsave_info); i++) {
+		int ret;
+		const xsave_proc_info_t *info = &fpu_xsave_info[i];
+		if (!info->xi_always && (info->xi_bits & bv) == 0)
+			continue;
+		bv &= ~info->xi_bits;
+
+		if (info->xi_signal_in == NULL)
+			continue;
+		ret = info->xi_signal_in(info, kuc, &ucx, fpu, &data, max_data);
+		if (ret != 0) {
+			return (ret);
+		}
+	}
+	ASSERT0(bv);
+
+	/*
+	 * As described in the big theory statement section 'Signal Handling and
+	 * the ucontext_t', we always remove UC_FPU from here as we've taken
+	 * care of reassembling it ourselves.
+	 */
+	kuc->uc_flags &= ~UC_FPU;
+	kuc->uc_xsave = (uintptr_t)fpu;
+
+	return (0);
+}
+
+/*
+ * This determines the size of the signal stack that we need for our custom form
+ * of the xsave state.
+ */
+size_t
+fpu_signal_size(struct _klwp *lwp)
+{
+	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
+	size_t len = sizeof (uc_xsave_t);
+	uint64_t xs_bv;
+
+	VERIFY3P(curthread, ==, lwptot(lwp));
+	VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+	ASSERT3U(fpu->fpu_flags & FPU_EN, ==, FPU_EN);
+
+	if (!fpu_xsave_enabled()) {
+		return (0);
+	}
+
+	kpreempt_disable();
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == FPU_EN) {
+		fp_save(fpu);
+	}
+
+	xs_bv = fpu->fpu_regs.kfpu_u.kfpu_xs->xs_header.xsh_xstate_bv;
+	for (size_t i = 0; i < ARRAY_SIZE(fpu_xsave_info); i++) {
+		size_t comp_size;
+
+		if (!fpu_signal_include(&fpu_xsave_info[i], xs_bv))
+			continue;
+
+		cpuid_get_xsave_info(fpu_xsave_info[i].xi_bits, &comp_size,
+		    NULL);
+		len += comp_size;
+	}
+
+	kpreempt_enable();
+	return (len);
+}
+
+/*
+ * This function is used in service of restorecontext() to set the specified
+ * thread's extended FPU state to the passed in data. Our assumptions at this
+ * point from the system are:
+ *
+ *   o Someone has already verified that the actual xsave header is correct.
+ *   o Any traditional XMM state that causes a #gp has been clamped.
+ *   o That data is basically the correct sized xsave state structure. Right now
+ *     that means it is not compressed and follows the CPUID-based rules for
+ *     constructing and laying out data.
+ *   o That the lwp argument does refer to the current thread.
+ *
+ * Our primary purpose here is to merge the current FPU state with what exists
+ * here. Right now, "merge", strictly speaking is just "replace". We can get
+ * away with just replacing everything because all we currently save are user
+ * states. If we start saving kernel states in here, this will get more nuanced
+ * and we will need to be more careful about how we store data here.
+ */
+void
+fpu_set_xsave(struct _klwp *lwp, const void *data)
+{
+	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
+	uint32_t status, xstatus;
+	struct xsave_state *dst_xsave;
+
+	ASSERT(fpu_xsave_enabled());
+	VERIFY3P(curthread, ==, lwptot(lwp));
+	VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+	ASSERT3U(fpu->fpu_flags & FPU_EN, ==, FPU_EN);
+
+	/*
+	 * We use fp_save() here rather than a stock fpdisable() so we can
+	 * attempt to honor our invariants that when the thread state has been
+	 * saved, the valid flag is set, even though we're going to be
+	 * overwriting it shortly. If we just called fpdisable() then we would
+	 * basically be asking for trouble.
+	 *
+	 * Because we are modifying the state here and we don't want the system
+	 * to end up in an odd state, we are being a little paranoid and
+	 * disabling preemption across this operation. In particular, once the
+	 * state is properly tagged with FPU_VALID, there should be no other way
+	 * that this thread can return to userland and get cleared out because
+	 * we're resetting its context; however, we let paranoia win out.
+	 */
+	kpreempt_disable();
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == FPU_EN) {
+		fp_save(fpu);
+	}
+
+	bcopy(data, lwp->lwp_pcb.pcb_fpu.fpu_regs.kfpu_u.kfpu_generic,
+	    cpuid_get_xsave_size());
+	dst_xsave = lwp->lwp_pcb.pcb_fpu.fpu_regs.kfpu_u.kfpu_generic;
+	status = dst_xsave->xs_fxsave.__fx_ign2[3]._l[0];
+	xstatus = dst_xsave->xs_fxsave.__fx_ign2[3]._l[1];
+	dst_xsave->xs_fxsave.__fx_ign2[3]._l[0] = 0;
+	dst_xsave->xs_fxsave.__fx_ign2[3]._l[1] = 0;
+
+	/*
+	 * These two status words are information that the kernel itself uses to
+	 * track additional information and is part of the traditional fpregset,
+	 * but is not part of our xregs information. Because we are setting this
+	 * state, we leave it up to the rest of the kernel to determine whether
+	 * this came from an fpregset_t or is being reset to the default of 0.
+	 */
+	fpu->fpu_regs.kfpu_status = status;
+	fpu->fpu_regs.kfpu_xstatus = xstatus;
+
+	fpu->fpu_flags |= FPU_VALID;
+	PCB_SET_UPDATE_FPU(&lwp->lwp_pcb);
+	kpreempt_enable();
+}
+
+/*
+ * Convert the current FPU state to the traditional fpregset_t. In the 64-bit
+ * kernel, this is just an fxsave_state with additional values for the status
+ * and xstatus members.
+ *
+ * This has the same nuance as the xregs cases discussed above, but is simpler
+ * in that we only need to handle the fxsave state, but more complicated because
+ * we need to check our save mechanism.
+ */
+void
+fpu_get_fpregset(struct _klwp *lwp, fpregset_t *fp)
+{
+	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
+
+	kpreempt_disable();
+	fp->fp_reg_set.fpchip_state.status = fpu->fpu_regs.kfpu_status;
+	fp->fp_reg_set.fpchip_state.xstatus = fpu->fpu_regs.kfpu_xstatus;
+
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == FPU_EN) {
+		/*
+		 * If we're requesting the fpregs of a thread that isn't
+		 * currently valid and isn't the one that we're executing, then
+		 * we consider getting this information to be a best-effort and
+		 * we will not stop the thread in question to serialize it,
+		 * which means possibly getting stale data. This is the
+		 * traditional semantics that the system has used to service
+		 * this for /proc.
+		 */
+		if (curthread == lwptot(lwp)) {
+			VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+			fp_save(fpu);
+		}
+	}
+
+	/*
+	 * If the FPU is not enabled and the state isn't valid (due to someone
+	 * else setting it), just copy the initial state.
+	 */
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == 0) {
+		bcopy(&sse_initial, fp, sizeof (sse_initial));
+		kpreempt_enable();
+		return;
+	}
+
+	/*
+	 * Given that we have an enabled FPU, we must look at the type of FPU
+	 * save mechanism to clean this up. In particular, while we can just
+	 * copy the save area with FXSAVE, with XSAVE we must carefully copy
+	 * only the bits that are valid and reset the rest to their default
+	 * state.
+	 */
+	switch (fp_save_mech) {
+	case FP_FXSAVE:
+		bcopy(fpu->fpu_regs.kfpu_u.kfpu_fx, fp,
+		    sizeof (struct fxsave_state));
+		break;
+	case FP_XSAVE:
+		fpu_xsave_to_fxsave(fpu->fpu_regs.kfpu_u.kfpu_xs,
+		    (struct fxsave_state *)fp);
+		break;
+	default:
+		panic("Invalid fp_save_mech");
+	}
+
+	kpreempt_enable();
+}
+
+/*
+ * This is a request to set the ABI fpregset_t into our actual hardware state.
+ * In the 64-bit kernel the first 512 bytes of the fpregset_t is the same as the
+ * 512-byte fxsave area.
+ */
+void
+fpu_set_fpregset(struct _klwp *lwp, const fpregset_t *fp)
+{
+	struct fpu_ctx *fpu = &lwp->lwp_pcb.pcb_fpu;
+
+	kpreempt_disable();
+	if ((fpu->fpu_flags & (FPU_EN | FPU_VALID)) == FPU_EN) {
+		/*
+		 * We always save the entire FPU. This is required if we're
+		 * using xsave. If we're using fxsave, we could skip the
+		 * 512-byte write and instead just disable the FPU since we'd be
+		 * replacing it all. For now we don't bother with more
+		 * conditional logic.
+		 */
+		VERIFY3P(curthread, ==, lwptot(lwp));
+		VERIFY0(lwptot(lwp)->t_flag & T_KFPU);
+		fp_save(fpu);
+	}
+
+	fpu->fpu_regs.kfpu_xstatus = fp->fp_reg_set.fpchip_state.xstatus;
+	fpu->fpu_regs.kfpu_status = fp->fp_reg_set.fpchip_state.status;
+	switch (fp_save_mech) {
+	case FP_FXSAVE:
+		bcopy(fp, fpu->fpu_regs.kfpu_u.kfpu_fx,
+		    sizeof (struct fxsave_state));
+		break;
+	case FP_XSAVE:
+		bcopy(fp, fpu->fpu_regs.kfpu_u.kfpu_xs,
+		    sizeof (struct fxsave_state));
+		fpu->fpu_regs.kfpu_u.kfpu_xs->xs_header.xsh_xstate_bv |=
+		    XFEATURE_LEGACY_FP | XFEATURE_SSE;
+		break;
+	default:
+		panic("Invalid fp_save_mech");
+	}
+
+	fpu->fpu_flags |= FPU_VALID;
+	PCB_SET_UPDATE_FPU(&lwp->lwp_pcb);
+	kpreempt_enable();
 }
--- old/usr/src/uts/i86pc/os/fpu_subr.c	Wed Apr 12 20:33:33 2023
+++ /dev/null	Wed Apr 12 20:33:32 2023
@@ -1,250 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License (the "License").
- * You may not use this file except in compliance with the License.
- *
- * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
- * or http://www.opensolaris.org/os/licensing.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information: Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- */
-
-/*
- * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2018, Joyent, Inc.
- */
-
-/*
- * Floating point configuration.
- */
-
-#include <sys/types.h>
-#include <sys/regset.h>
-#include <sys/privregs.h>
-#include <sys/x86_archext.h>
-#include <sys/archsystm.h>
-#include <sys/fp.h>
-#include <sys/cmn_err.h>
-#include <sys/exec.h>
-
-#define	XMM_ALIGN	16
-
-/*
- * See section 10.5.1 in the Intel 64 and IA-32 Architectures Software
- * Developer’s Manual, Volume 1.
- */
-#define	FXSAVE_ALIGN	16
-
-/*
- * See section 13.4 in the Intel 64 and IA-32 Architectures Software
- * Developer’s Manual, Volume 1.
- */
-#define	XSAVE_ALIGN	64
-
-/*
- * If fpu_exists is non-zero, fpu_probe will attempt to use any
- * hardware FPU (subject to other constraints, see below).  If
- * fpu_exists is zero, fpu_probe will report that there is no
- * FPU even if there is one.
- */
-int fpu_exists = 1;
-
-int fp_kind = FP_387;
-
-/*
- * The kind of FPU we advertise to rtld so it knows what to do on context
- * switch.
- */
-int fp_elf = AT_386_FPINFO_FXSAVE;
-
-/*
- * Mechanism to save FPU state.
- */
-int fp_save_mech = FP_FXSAVE;
-
-/*
- * The variable fpu_ignored is provided to allow other code to
- * determine whether emulation is being done because there is
- * no FPU or because of an override requested via /etc/system.
- */
-int fpu_ignored = 0;
-
-/*
- * Used by ppcopy and ppzero to determine whether or not to use the
- * SSE-based pagecopy and pagezero routines
- */
-int use_sse_pagecopy = 0;
-int use_sse_pagezero = 0;
-int use_sse_copy = 0;
-
-#if defined(__xpv)
-
-/*
- * Use of SSE or otherwise is forcibly configured for us by the hypervisor.
- */
-
-#define	ENABLE_SSE()
-#define	DISABLE_SSE()
-
-#else	/* __xpv */
-
-#define	ENABLE_SSE()	setcr4(CR4_ENABLE_SSE_FLAGS(getcr4()))
-#define	DISABLE_SSE()	setcr4(CR4_DISABLE_SSE_FLAGS(getcr4()))
-
-#endif	/* __xpv */
-
-/*
- * Try and figure out what kind of FP capabilities we have, and
- * set up the control registers accordingly.
- */
-void
-fpu_probe(void)
-{
-	if (fpu_initial_probe() != 0)
-		goto nofpu;
-
-	if (fpu_exists == 0) {
-		fpu_ignored = 1;
-		goto nofpu;
-	}
-
-#ifndef __xpv
-	/*
-	 * Check and see if the fpu is present by looking
-	 * at the "extension type" bit.  (While this used to
-	 * indicate a 387DX coprocessor in days gone by,
-	 * it's forced on by modern implementations for
-	 * compatibility.)
-	 */
-	if ((getcr0() & CR0_ET) == 0)
-		goto nofpu;
-#endif
-
-	/* Use the more complex exception clearing code if necessary */
-	if (cpuid_need_fp_excp_handling())
-		fpsave_ctxt = fpxsave_excp_clr_ctxt;
-
-	/*
-	 * SSE and SSE2 are required for the 64-bit ABI.
-	 *
-	 * If they're not present, we can in principal run
-	 * 32-bit userland, though 64-bit processes will be hosed.
-	 *
-	 * (Perhaps we should complain more about this case!)
-	 */
-	if (is_x86_feature(x86_featureset, X86FSET_SSE) &&
-	    is_x86_feature(x86_featureset, X86FSET_SSE2)) {
-		fp_kind |= __FP_SSE;
-		ENABLE_SSE();
-
-		if (is_x86_feature(x86_featureset, X86FSET_AVX)) {
-			ASSERT(is_x86_feature(x86_featureset, X86FSET_XSAVE));
-			fp_kind |= __FP_AVX;
-		}
-
-		if (is_x86_feature(x86_featureset, X86FSET_XSAVE)) {
-			fp_save_mech = FP_XSAVE;
-			fp_elf = AT_386_FPINFO_XSAVE;
-			if (is_x86_feature(x86_featureset, X86FSET_XSAVEOPT)) {
-				/*
-				 * Use the more complex exception
-				 * clearing code if necessary.
-				 */
-				if (cpuid_need_fp_excp_handling()) {
-					fpsave_ctxt = xsaveopt_excp_clr_ctxt;
-					fp_elf = AT_386_FPINFO_XSAVE_AMD;
-				} else {
-					fpsave_ctxt = xsaveopt_ctxt;
-				}
-				xsavep = xsaveopt;
-			} else {
-				/*
-				 * Use the more complex exception
-				 * clearing code if necessary.
-				 */
-				if (cpuid_need_fp_excp_handling()) {
-					fpsave_ctxt = xsave_excp_clr_ctxt;
-					fp_elf = AT_386_FPINFO_XSAVE_AMD;
-				} else {
-					fpsave_ctxt = xsave_ctxt;
-				}
-			}
-			fprestore_ctxt = xrestore_ctxt;
-			fpsave_cachep = kmem_cache_create("xsave_cache",
-			    cpuid_get_xsave_size(), XSAVE_ALIGN,
-			    NULL, NULL, NULL, NULL, NULL, 0);
-		} else {
-			/* fp_save_mech defaults to FP_FXSAVE */
-			fpsave_cachep = kmem_cache_create("fxsave_cache",
-			    sizeof (struct fxsave_state), FXSAVE_ALIGN,
-			    NULL, NULL, NULL, NULL, NULL, 0);
-			fp_elf = AT_386_FPINFO_FXSAVE;
-		}
-	}
-
-	if (is_x86_feature(x86_featureset, X86FSET_SSE2)) {
-		use_sse_pagecopy = use_sse_pagezero = use_sse_copy = 1;
-	}
-
-	if (fp_kind & __FP_SSE) {
-		struct fxsave_state *fx;
-		uint8_t fxsave_state[sizeof (struct fxsave_state) + XMM_ALIGN];
-
-		/*
-		 * Extract the mxcsr mask from our first fxsave
-		 */
-		fx = (void *)(((uintptr_t)(&fxsave_state[0]) +
-		    XMM_ALIGN) & ~(XMM_ALIGN - 1ul));
-
-		fx->fx_mxcsr_mask = 0;
-		fxsave_insn(fx);
-		if (fx->fx_mxcsr_mask != 0) {
-			/*
-			 * Override default mask initialized in fpu.c
-			 */
-			sse_mxcsr_mask = fx->fx_mxcsr_mask;
-		}
-	}
-
-	setcr0(CR0_ENABLE_FPU_FLAGS(getcr0()));
-	return;
-
-	/*
-	 * No FPU hardware present
-	 */
-nofpu:
-	setcr0(CR0_DISABLE_FPU_FLAGS(getcr0()));
-	DISABLE_SSE();
-	fp_kind = FP_NO;
-	fpu_exists = 0;
-}
-
-/*
- * Fill in FPU information that is required by exec.
- */
-void
-fpu_auxv_info(int *typep, size_t *lenp)
-{
-	*typep = fp_elf;
-	switch (fp_save_mech) {
-	case FP_FXSAVE:
-		*lenp = sizeof (struct fxsave_state);
-		break;
-	case FP_XSAVE:
-		*lenp = cpuid_get_xsave_size();
-		break;
-	default:
-		*lenp = 0;
-		break;
-	}
-}
--- /dev/null	Wed Apr 12 20:33:32 2023
+++ new/usr/src/uts/intel/os/fpu_subr.c	Wed Apr 12 20:33:33 2023
@@ -0,0 +1,219 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, Joyent, Inc.
+ */
+
+/*
+ * Floating point configuration.
+ */
+
+#include <sys/types.h>
+#include <sys/regset.h>
+#include <sys/privregs.h>
+#include <sys/x86_archext.h>
+#include <sys/archsystm.h>
+#include <sys/fp.h>
+#include <sys/cmn_err.h>
+#include <sys/exec.h>
+
+#define	XMM_ALIGN	16
+
+/*
+ * See section 10.5.1 in the Intel 64 and IA-32 Architectures Software
+ * Developer’s Manual, Volume 1.
+ */
+#define	FXSAVE_ALIGN	16
+
+/*
+ * See section 13.4 in the Intel 64 and IA-32 Architectures Software
+ * Developer’s Manual, Volume 1.
+ */
+#define	XSAVE_ALIGN	64
+
+/*
+ * If fpu_exists is non-zero, fpu_probe will attempt to use any
+ * hardware FPU (subject to other constraints, see below).  If
+ * fpu_exists is zero, fpu_probe will report that there is no
+ * FPU even if there is one.
+ */
+int fpu_exists = 1;
+
+int fp_kind = FP_387;
+
+/*
+ * The variable fpu_ignored is provided to allow other code to
+ * determine whether emulation is being done because there is
+ * no FPU or because of an override requested via /etc/system.
+ */
+int fpu_ignored = 0;
+
+/*
+ * Used by ppcopy and ppzero to determine whether or not to use the
+ * SSE-based pagecopy and pagezero routines
+ */
+int use_sse_pagecopy = 0;
+int use_sse_pagezero = 0;
+int use_sse_copy = 0;
+
+#if defined(__xpv)
+
+/*
+ * Use of SSE or otherwise is forcibly configured for us by the hypervisor.
+ */
+
+#define	ENABLE_SSE()
+#define	DISABLE_SSE()
+
+#else	/* __xpv */
+
+#define	ENABLE_SSE()	setcr4(CR4_ENABLE_SSE_FLAGS(getcr4()))
+#define	DISABLE_SSE()	setcr4(CR4_DISABLE_SSE_FLAGS(getcr4()))
+
+#endif	/* __xpv */
+
+/*
+ * Try and figure out what kind of FP capabilities we have, and
+ * set up the control registers accordingly.
+ */
+void
+fpu_probe(void)
+{
+	if (fpu_initial_probe() != 0)
+		goto nofpu;
+
+	if (fpu_exists == 0) {
+		fpu_ignored = 1;
+		goto nofpu;
+	}
+
+#ifndef __xpv
+	/*
+	 * Check and see if the fpu is present by looking
+	 * at the "extension type" bit.  (While this used to
+	 * indicate a 387DX coprocessor in days gone by,
+	 * it's forced on by modern implementations for
+	 * compatibility.)
+	 */
+	if ((getcr0() & CR0_ET) == 0)
+		goto nofpu;
+#endif
+
+	/* Use the more complex exception clearing code if necessary */
+	if (cpuid_need_fp_excp_handling())
+		fpsave_ctxt = fpxsave_excp_clr_ctxt;
+
+	/*
+	 * SSE and SSE2 are required for the 64-bit ABI.
+	 *
+	 * If they're not present, we can in principal run
+	 * 32-bit userland, though 64-bit processes will be hosed.
+	 *
+	 * (Perhaps we should complain more about this case!)
+	 */
+	if (is_x86_feature(x86_featureset, X86FSET_SSE) &&
+	    is_x86_feature(x86_featureset, X86FSET_SSE2)) {
+		fp_kind |= __FP_SSE;
+		ENABLE_SSE();
+
+		if (is_x86_feature(x86_featureset, X86FSET_AVX)) {
+			ASSERT(is_x86_feature(x86_featureset, X86FSET_XSAVE));
+			fp_kind |= __FP_AVX;
+		}
+
+		if (is_x86_feature(x86_featureset, X86FSET_XSAVE)) {
+			fp_save_mech = FP_XSAVE;
+			fp_elf = AT_386_FPINFO_XSAVE;
+			if (is_x86_feature(x86_featureset, X86FSET_XSAVEOPT)) {
+				/*
+				 * Use the more complex exception
+				 * clearing code if necessary.
+				 */
+				if (cpuid_need_fp_excp_handling()) {
+					fpsave_ctxt = xsaveopt_excp_clr_ctxt;
+					fp_elf = AT_386_FPINFO_XSAVE_AMD;
+				} else {
+					fpsave_ctxt = xsaveopt_ctxt;
+				}
+				xsavep = xsaveopt;
+			} else {
+				/*
+				 * Use the more complex exception
+				 * clearing code if necessary.
+				 */
+				if (cpuid_need_fp_excp_handling()) {
+					fpsave_ctxt = xsave_excp_clr_ctxt;
+					fp_elf = AT_386_FPINFO_XSAVE_AMD;
+				} else {
+					fpsave_ctxt = xsave_ctxt;
+				}
+			}
+			fprestore_ctxt = xrestore_ctxt;
+			fpsave_cachep = kmem_cache_create("xsave_cache",
+			    cpuid_get_xsave_size(), XSAVE_ALIGN,
+			    NULL, NULL, NULL, NULL, NULL, 0);
+		} else {
+			/* fp_save_mech defaults to FP_FXSAVE */
+			fpsave_cachep = kmem_cache_create("fxsave_cache",
+			    sizeof (struct fxsave_state), FXSAVE_ALIGN,
+			    NULL, NULL, NULL, NULL, NULL, 0);
+			fp_elf = AT_386_FPINFO_FXSAVE;
+		}
+	}
+
+	if (is_x86_feature(x86_featureset, X86FSET_SSE2)) {
+		use_sse_pagecopy = use_sse_pagezero = use_sse_copy = 1;
+	}
+
+	if (fp_kind & __FP_SSE) {
+		struct fxsave_state *fx;
+		uint8_t fxsave_state[sizeof (struct fxsave_state) + XMM_ALIGN];
+
+		/*
+		 * Extract the mxcsr mask from our first fxsave
+		 */
+		fx = (void *)(((uintptr_t)(&fxsave_state[0]) +
+		    XMM_ALIGN) & ~(XMM_ALIGN - 1ul));
+
+		fx->fx_mxcsr_mask = 0;
+		fxsave_insn(fx);
+		if (fx->fx_mxcsr_mask != 0) {
+			/*
+			 * Override default mask initialized in fpu.c
+			 */
+			sse_mxcsr_mask = fx->fx_mxcsr_mask;
+		}
+	}
+
+	setcr0(CR0_ENABLE_FPU_FLAGS(getcr0()));
+	return;
+
+	/*
+	 * No FPU hardware present
+	 */
+nofpu:
+	setcr0(CR0_DISABLE_FPU_FLAGS(getcr0()));
+	DISABLE_SSE();
+	fp_kind = FP_NO;
+	fpu_exists = 0;
+}
--- old/usr/src/uts/intel/os/sendsig.c	Wed Apr 12 20:33:33 2023
+++ new/usr/src/uts/intel/os/sendsig.c	Wed Apr 12 20:33:33 2023
@@ -31,6 +31,10 @@
 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T   */
 /*	All Rights Reserved   */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/sysmacros.h>
@@ -112,9 +116,10 @@
  *		<128 bytes of untouched stack space>
  *		<a siginfo_t [optional]>
  *		<a ucontext_t>
- *		<siginfo_t *>
- *		<signal number>
- * new %rsp:	<return address (deliberately invalid)>
+ *		<a ucontext_t's xsave state>
+ *		<siginfo_t *>                             ---+
+ *		<signal number>                              | sigframe
+ * new %rsp:	<return address (deliberately invalid)>   ---+
  *
  * The signal number and siginfo_t pointer are only pushed onto the stack in
  * order to allow stack backtraces.  The actual signal handling code expects the
@@ -130,8 +135,10 @@
 int
 sendsig(int sig, k_siginfo_t *sip, void (*hdlr)())
 {
-	volatile int minstacksz;
-	int newstack;
+	volatile size_t minstacksz;
+	boolean_t newstack;
+	size_t xsave_size;
+	int ret;
 	label_t ljb;
 	volatile caddr_t sp;
 	caddr_t fp;
@@ -171,14 +178,26 @@
 	 * which must be 16-byte aligned. Because of this, for correct
 	 * alignment, sigframe must be a multiple of 8-bytes in length, but
 	 * not 16-bytes. This will place ucontext_t at a nice 16-byte boundary.
+	 *
+	 * When we move onto the xsave state, right now, we don't guarantee any
+	 * alignment of the resulting data, but we will ensure that the
+	 * resulting sp does have proper alignment. This will ensure that the
+	 * guarantee on the ucontex_t is not violated.
 	 */
 
-	/* LINTED: logical expression always true: op "||" */
-	ASSERT((sizeof (struct sigframe) % 16) == 8);
+	CTASSERT((sizeof (struct sigframe) % 16) == 8);
 
 	minstacksz = sizeof (struct sigframe) + SA(sizeof (*uc));
 	if (sip != NULL)
 		minstacksz += SA(sizeof (siginfo_t));
+
+	if (fpu_xsave_enabled()) {
+		xsave_size = SA(fpu_signal_size(lwp));
+		minstacksz += xsave_size;
+	} else {
+		xsave_size = 0;
+	}
+
 	ASSERT((minstacksz & (STACK_ENTRY_ALIGN - 1ul)) == 0);
 
 	/*
@@ -295,16 +314,26 @@
 	} else
 		sip_addr = NULL;
 
+	no_fault();
+
 	/*
-	 * save the current context on the user stack directly after the
-	 * sigframe. Since sigframe is 8-byte-but-not-16-byte aligned,
-	 * and since sizeof (struct sigframe) is 24, this guarantees
-	 * 16-byte alignment for ucontext_t and its %xmm registers.
+	 * Save the current context on the user stack directly after the
+	 * sigframe. Since sigframe is 8-byte-but-not-16-byte aligned, and since
+	 * sizeof (struct sigframe) is 24, this guarantees 16-byte alignment for
+	 * ucontext_t and its %xmm registers. The xsave state part of the
+	 * ucontext_t may be inbetween these two. However, we have ensured that
+	 * the size of the stack space is 16-byte aligned as the actual size may
+	 * vary.
 	 */
-	uc = (ucontext_t *)(sp + sizeof (struct sigframe));
 	tuc = kmem_alloc(sizeof (*tuc), KM_SLEEP);
-	no_fault();
-	savecontext(tuc, &lwp->lwp_sigoldmask);
+	if (xsave_size != 0) {
+		tuc->uc_xsave = (unsigned long)(sp + sizeof (struct sigframe));
+	}
+	uc = (ucontext_t *)(sp + sizeof (struct sigframe) + xsave_size);
+	ret = savecontext(tuc, &lwp->lwp_sigoldmask, SAVECTXT_F_EXTD |
+	    SAVECTXT_F_ONFAULT);
+	if (ret != 0)
+		goto postfault;
 	if (on_fault(&ljb))
 		goto badstack;
 	copyout_noerr(tuc, uc, sizeof (*tuc));
@@ -378,6 +407,7 @@
 
 badstack:
 	no_fault();
+postfault:
 	if (watched)
 		watch_enable_addr((caddr_t)sp, minstacksz, S_WRITE);
 	if (tuc)
@@ -399,6 +429,7 @@
  * old %esp:
  *		<a siginfo32_t [optional]>
  *		<a ucontext32_t>
+ *		<a ucontext32_t's xsave state>
  *		<pointer to that ucontext32_t>
  *		<pointer to that siginfo32_t>
  *		<signo>
@@ -414,8 +445,10 @@
 int
 sendsig32(int sig, k_siginfo_t *sip, void (*hdlr)())
 {
-	volatile int minstacksz;
-	int newstack;
+	volatile size_t minstacksz;
+	boolean_t newstack;
+	size_t xsave_size;
+	int ret;
 	label_t ljb;
 	volatile caddr_t sp;
 	caddr_t fp;
@@ -434,6 +467,13 @@
 	minstacksz = SA32(sizeof (struct sigframe32)) + SA32(sizeof (*uc));
 	if (sip != NULL)
 		minstacksz += SA32(sizeof (siginfo32_t));
+
+	if (fpu_xsave_enabled()) {
+		xsave_size = SA32(fpu_signal_size(lwp));
+		minstacksz += xsave_size;
+	} else {
+		xsave_size = 0;
+	}
 	ASSERT((minstacksz & (STACK_ALIGN32 - 1)) == 0);
 
 	/*
@@ -540,13 +580,20 @@
 		}
 	} else
 		sip_addr = NULL;
+	no_fault();
 
 	/* save the current context on the user stack */
+	tuc = kmem_alloc(sizeof (*tuc), KM_SLEEP);
 	fp -= SA32(sizeof (*tuc));
 	uc = (ucontext32_t *)fp;
-	tuc = kmem_alloc(sizeof (*tuc), KM_SLEEP);
-	no_fault();
-	savecontext32(tuc, &lwp->lwp_sigoldmask);
+	if (xsave_size != 0) {
+		fp -= xsave_size;
+		tuc->uc_xsave = (int32_t)(uintptr_t)fp;
+	}
+	ret = savecontext32(tuc, &lwp->lwp_sigoldmask, SAVECTXT_F_EXTD |
+	    SAVECTXT_F_ONFAULT);
+	if (ret != 0)
+		goto postfault;
 	if (on_fault(&ljb))
 		goto badstack;
 	copyout_noerr(tuc, uc, sizeof (*tuc));
@@ -620,6 +667,7 @@
 
 badstack:
 	no_fault();
+postfault:
 	if (watched)
 		watch_enable_addr((caddr_t)sp, minstacksz, S_WRITE);
 	if (tuc)
--- old/usr/src/uts/intel/sys/fp.h	Wed Apr 12 20:33:33 2023
+++ new/usr/src/uts/intel/sys/fp.h	Wed Apr 12 20:33:33 2023
@@ -21,7 +21,7 @@
 /*
  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2018, Joyent, Inc.
- * Copyright 2022 Oxide Computer Company
+ * Copyright 2023 Oxide Computer Company
  *
  * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved.
  */
@@ -135,10 +135,21 @@
  * - round to nearest or even
  * - 64-bit double precision
  * - all exceptions masked
+ *
+ * The 4th ed. SVR4 ABI didn't discuss the value of reserved bits. The ISA
+ * defines bit 6 (0x40) as reserved, but also that it is set (rather than clear,
+ * like many other Reserved bits). We preserve that in our value here.
  */
-#define	FPU_CW_INIT	0x133f
+#define	FPU_CW_INIT	0x137f
 
 /*
+ * This is the Intel mandated form of the default value of the x87 control word.
+ * This is different from what we use and should only be used in the context of
+ * representing that default state (e.g. in /proc xregs).
+ */
+#define	FPU_CW_INIT_HW	0x037f
+
+/*
  * masks and flags for SSE/SSE2 MXCSR
  */
 #define	SSE_IE	0x00000001	/* invalid operation			*/
@@ -301,9 +312,6 @@
 	union _kfpu_u {
 		void *kfpu_generic;
 		struct fxsave_state *kfpu_fx;
-#if defined(__i386)
-		struct fnsave_state *kfpu_fn;
-#endif
 		struct xsave_state *kfpu_xs;
 	} kfpu_u;
 	uint32_t kfpu_status;		/* saved at #mf exception */
@@ -310,9 +318,11 @@
 	uint32_t kfpu_xstatus;		/* saved at #xm exception */
 } kfpu_t;
 
-extern int fp_kind;		/* kind of fp support			*/
-extern int fp_save_mech;	/* fp save/restore mechanism		*/
-extern int fpu_exists;		/* FPU hw exists			*/
+extern int fp_kind;		/* kind of fp support */
+extern int fp_save_mech;	/* fp save/restore mechanism */
+extern int fpu_exists;		/* FPU hw exists */
+extern int fp_elf;		/* FP elf type */
+extern uint64_t xsave_bv_all;	/* Set of enabed xcr0 values */
 
 #ifdef _KERNEL
 
@@ -325,6 +335,7 @@
 extern uint_t fpu_initial_probe(void);
 
 extern void fpu_auxv_info(int *, size_t *);
+extern boolean_t fpu_xsave_enabled(void);
 
 extern void fpnsave_ctxt(void *);
 extern void fpxsave_ctxt(void *);
@@ -341,8 +352,6 @@
 extern void (*fprestore_ctxt)(void *);
 
 extern void fxsave_insn(struct fxsave_state *);
-extern void fpsave(struct fnsave_state *);
-extern void fprestore(struct fnsave_state *);
 extern void fpxsave(struct fxsave_state *);
 extern void fpxrestore(struct fxsave_state *);
 extern void xsave(struct xsave_state *, uint64_t);
@@ -373,6 +382,22 @@
 extern const struct fxsave_state sse_initial;
 extern const struct xsave_state avx_initial;
 
+struct proc;
+struct ucontext;
+extern void fpu_proc_xregs_info(struct proc *, uint32_t *, uint32_t *,
+    uint32_t *);
+extern size_t fpu_proc_xregs_max_size(void);
+extern void fpu_proc_xregs_get(struct _klwp *, void *);
+extern int fpu_proc_xregs_set(struct _klwp *, void *);
+extern int fpu_signal_copyin(struct _klwp *, struct ucontext *);
+typedef int (*fpu_copyout_f)(const void *, void *, size_t);
+extern int fpu_signal_copyout(struct _klwp *, uintptr_t, fpu_copyout_f);
+extern void fpu_set_xsave(struct _klwp *, const void *);
+extern size_t fpu_signal_size(struct _klwp *);
+
+extern void fpu_get_fpregset(struct _klwp *, fpregset_t *);
+extern void fpu_set_fpregset(struct _klwp *, const fpregset_t *);
+
 #endif	/* _KERNEL */
 
 #ifdef __cplusplus
--- old/usr/src/uts/intel/sys/pcb.h	Wed Apr 12 20:33:33 2023
+++ new/usr/src/uts/intel/sys/pcb.h	Wed Apr 12 20:33:33 2023
@@ -41,10 +41,8 @@
 typedef struct fpu_ctx {
 	kfpu_t		fpu_regs;	/* kernel save area for FPU */
 	uint64_t	fpu_xsave_mask;	/* xsave mask for FPU/SSE/AVX */
-#if defined(__i386)
-	uint64_t	fpu_padding;	/* fix 32bit libmicro regression */
-#endif
 	uint_t		fpu_flags;	/* FPU state flags */
+	void		*fpu_signal;	/* copyin area for signal handling */
 } fpu_ctx_t;
 
 typedef struct pcb {
--- old/usr/src/uts/intel/sys/procfs_isa.h	Wed Apr 12 20:33:34 2023
+++ new/usr/src/uts/intel/sys/procfs_isa.h	Wed Apr 12 20:33:34 2023
@@ -24,6 +24,10 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #ifndef _SYS_PROCFS_ISA_H
 #define	_SYS_PROCFS_ISA_H
 
@@ -112,6 +116,141 @@
 #define	R_R1	EDX
 #endif
 
+/*
+ * The x86 xregs structure is a blob of data that contains a header with several
+ * descriptors that describe the region of additional data that corresponds to
+ * it. Effectively this looks like:
+ *
+ * 0  +-----------------+
+ *    | prxregset_hdr_t |
+ *    +-----------------+
+ *    | Info 0 (XCR)    |-------+
+ *    +-----------------+       |
+ *    | Info 1 (XSAVE)  |----------+
+ *    +-----------------+       |  |
+ *           ...                |  |
+ *    +-----------------+       |  |
+ *    | Info n (Hi ZMM) |-------------+
+ *    +-----------------+       |  |  |
+ *    +-----------------+       |  |  |
+ *    | prxregset_xcr_t |<------+  |  |
+ *    +-----------------+          |  |
+ *    +-------------------+        |  |
+ *    | prxregset_xsave_t |<-------+  |
+ *    |                   |           |
+ *    | XMM + xsave       |           |
+ *    +-------------------+           |
+ *           ...                      |
+ *    +---------------------+         |
+ *    | prxregset_hi_zmm_t  |<--------+
+ *    |                     |
+ *    | 1 KiB %zmm16-%zmm31 |
+ *    +---------------------+
+ *
+ * The actual structure size will vary based on the CPU features present. For
+ * more information, see proc(5). When adding structures, please make sure all
+ * structures are multiples of 16 bytes (0x10) so as to ensure alignment.
+ */
+typedef struct prxregset prxregset_t;
+
+#define	PRX_INFO_XCR	0x01
+#define	PRX_INFO_XSAVE	0x02
+#define	PRX_INFO_YMM	0x03
+#define	PRX_INFO_OPMASK	0x04
+#define	PRX_INFO_ZMM	0x05
+#define	PRX_INFO_HI_ZMM	0x06
+
+typedef struct prxregset_info {
+	uint32_t pri_type;
+	uint32_t pri_flags;
+	uint32_t pri_size;
+	uint32_t pri_offset;
+} prxregset_info_t;
+
+#define	PR_TYPE_XSAVE	0x01
+
+typedef struct prxregset_hdr {
+	uint32_t	pr_type;
+	uint32_t	pr_size;
+	uint32_t	pr_flags;
+	uint32_t	pr_pad[4];
+	uint32_t	pr_ninfo;
+#if defined(_STDC_C99) || defined(__C99FEATURES__)
+	prxregset_info_t pr_info[];
+#endif
+} prxregset_hdr_t;
+
+typedef struct prxregset_xcr {
+	uint64_t	prx_xcr_xcr0;
+	uint64_t	prx_xcr_xfd;
+	uint64_t	prx_xcr_pad[2];
+} prxregset_xcr_t;
+
+typedef struct prxregset_xsave {
+	uint16_t	prx_fx_fcw;
+	uint16_t	prx_fx_fsw;
+	uint16_t	prx_fx_fctw;	/* compressed tag word */
+	uint16_t	prx_fx_fop;
+#if defined(__amd64)
+	uint64_t	prx_fx_rip;
+	uint64_t	prx_fx_rdp;
+#else
+	uint32_t	prx_fx_eip;
+	uint16_t	prx_fx_cs;
+	uint16_t	__prx_fx_ign0;
+	uint32_t	prx_fx_dp;
+	uint16_t	prx_fx_ds;
+	uint16_t	__prx_fx_ign1;
+#endif
+	uint32_t	prx_fx_mxcsr;
+	uint32_t	prx_fx_mxcsr_mask;
+	union {
+		uint16_t prx_fpr_16[5];	/* 80-bits of x87 state */
+		u_longlong_t prx_fpr_mmx;	/* 64-bit mmx register */
+		uint32_t _prx__fpr_pad[4];	/* (pad out to 128-bits) */
+	} fx_st[8];
+#if defined(__amd64)
+	upad128_t	prx_fx_xmm[16];	/* 128-bit registers */
+	upad128_t	__prx_fx_ign2[6];
+#else
+	upad128_t	prx_fx_xmm[8];	/* 128-bit registers */
+	upad128_t	__prx_fx_ign2[14];
+#endif
+	uint64_t	prx_xsh_xstate_bv;
+	uint64_t	prx_xsh_xcomp_bv;
+	uint64_t	prx_xsh_reserved[6];
+} prxregset_xsave_t;
+
+typedef struct prxregset_ymm {
+#if defined(__amd64)
+	upad128_t	prx_ymm[16];
+#else
+	upad128_t	prx_ymm[8];
+	upad128_t	prx_rsvd[8];
+#endif
+} prxregset_ymm_t;
+
+typedef struct prxregset_opmask {
+	uint64_t	prx_opmask[8];
+} prxregset_opmask_t;
+
+typedef struct prxregset_zmm {
+#if defined(__amd64)
+	upad256_t	prx_zmm[16];
+#else
+	upad256_t	prx_zmm[8];
+	upad256_t	prx_rsvd[8];
+#endif
+} prxregset_zmm_t;
+
+typedef struct prxregset_hi_zmm {
+#if defined(__amd64)
+	upad512_t	prx_hi_zmm[16];
+#else
+	upad512_t	prx_rsvd[16];
+#endif
+} prxregset_hi_zmm_t;
+
 #ifdef	__cplusplus
 }
 #endif
--- old/usr/src/uts/intel/sys/ucontext.h	Wed Apr 12 20:33:34 2023
+++ new/usr/src/uts/intel/sys/ucontext.h	Wed Apr 12 20:33:34 2023
@@ -22,6 +22,7 @@
 /*
  * Copyright 2015 Joyent, Inc.
  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2023 Oxide Computer Company
  *
  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
@@ -82,18 +83,19 @@
 #endif
 	unsigned long	uc_flags;
 	ucontext_t	*uc_link;
-	sigset_t   	uc_sigmask;
-	stack_t 	uc_stack;
-	mcontext_t 	uc_mcontext;
+	sigset_t	uc_sigmask;
+	stack_t		uc_stack;
+	mcontext_t	uc_mcontext;
 	/*
-	 * The Intel386 ABI specification includes a 5-element array of longs
-	 * called "uc_filler", padding the size of the struct to 512 bytes.  To
-	 * allow zone brands to communicate extra data right the way through
-	 * the signal handling process, from sigacthandler to setcontext, we
-	 * steal the first three of these longs as a brand-private member.
+	 * The first three entries have been borrowed by the lx brand right now.
+	 * That should be consolidated into a single uc_brand entry with a
+	 * UC_BRAND flag. Until such time, we leave them as is.
+	 *
+	 * We rename those first three entries here for SmartOS.
 	 */
 	void		*uc_brand_data[3];
-	long		uc_filler[2];
+	long		uc_xsave;
+	long		uc_filler1;
 };
 #if defined(_SYSCALL32)
 
@@ -106,7 +108,8 @@
 	stack32_t	uc_stack;
 	mcontext32_t	uc_mcontext;
 	caddr32_t	uc_brand_data[3];
-	int32_t		uc_filler[2];
+	int32_t		uc_xsave;
+	int32_t		uc_filler1;
 } ucontext32_t;
 
 #if defined(_KERNEL)
@@ -121,6 +124,7 @@
 #define	SETCONTEXT	1
 #define	GETUSTACK	2
 #define	SETUSTACK	3
+#define	GETCONTEXT_EXTD	4
 
 /*
  * values for uc_flags
@@ -134,6 +138,7 @@
 #define	UC_CPU		0x04
 #define	UC_MAU		0x08
 #define	UC_FPU		UC_MAU
+#define	UC_XSAVE	0x10
 
 #define	UC_MCONTEXT	(UC_CPU|UC_FPU)
 
@@ -145,11 +150,44 @@
 #endif /* !defined(_XPG4_2) || defined(__EXTENSIONS__) */
 
 #ifdef _KERNEL
-void savecontext(ucontext_t *, const k_sigset_t *);
+/*
+ * This structure is the private header for the xsave data that we end up
+ * sending to the stack. This is basically our own compressed form. See,
+ * uts/intel/os/fpu.c for more information.
+ */
+#define	UC_XSAVE_VERS	(('u' << 24) | ('c' << 16) | 0x01)
+typedef struct uc_xsave {
+	uint32_t ucx_vers;
+	uint32_t ucx_len;
+	uint64_t ucx_bv;
+} uc_xsave_t;
+
+typedef enum {
+	/*
+	 * Do a boring old savecontext() where we assume that only the data
+	 * structure that we're given must be filled in.
+	 */
+	SAVECTXT_F_NONE = 0,
+	/*
+	 * Indicate that we should treat the ucontext_t as having valid user
+	 * pointers for copying out extended state. Currently this means that we
+	 * treat the uc_xsave member as something that points to a user address.
+	 */
+	SAVECTXT_F_EXTD	= 1 << 0,
+	/*
+	 * This indicates that we shouldn't do normal copyout handling and need
+	 * to actually avoid potentially triggering a watchpoint because we're
+	 * probably in signal handling context.
+	 */
+	SAVECTXT_F_ONFAULT = 1 << 1
+} savecontext_flags_t;
+
+int savecontext(ucontext_t *, const k_sigset_t *, savecontext_flags_t);
 void restorecontext(ucontext_t *);
 
 #ifdef _SYSCALL32
-extern void savecontext32(ucontext32_t *, const k_sigset_t *);
+extern int savecontext32(ucontext32_t *, const k_sigset_t *,
+    savecontext_flags_t);
 #endif
 #endif
 
--- old/usr/src/uts/intel/sys/x86_archext.h	Wed Apr 12 20:33:34 2023
+++ new/usr/src/uts/intel/sys/x86_archext.h	Wed Apr 12 20:33:34 2023
@@ -1437,7 +1437,7 @@
 
 /*
  * xgetbv/xsetbv support
- * See section 13.3 in vol. 1 of the Intel devlopers manual.
+ * See section 13.3 in vol. 1 of the Intel Developer's manual.
  */
 
 #define	XFEATURE_ENABLED_MASK	0x0
@@ -1445,13 +1445,27 @@
  * XFEATURE_ENABLED_MASK values (eax)
  * See setup_xfem().
  */
-#define	XFEATURE_LEGACY_FP	0x1
-#define	XFEATURE_SSE		0x2
-#define	XFEATURE_AVX		0x4
-#define	XFEATURE_MPX		0x18	/* 2 bits, both 0 or 1 */
-#define	XFEATURE_AVX512		0xe0	/* 3 bits, all 0 or 1 */
+#define	XFEATURE_LEGACY_FP	(1 << 0)
+#define	XFEATURE_SSE		(1 << 1)
+#define	XFEATURE_AVX		(1 << 2)
+/*
+ * MPX is meant to be all or nothing, therefore for most of the kernel use the
+ * following definition.
+ */
+#define	XFEATURE_MPX_BNDREGS	(1 << 3)
+#define	XFEATURE_MPX_BNDCSR	(1 << 4)
+#define	XFEATURE_MPX		(XFEATURE_MPX_BNDREGS | XFEATURE_MPX_BNDCSR)
+/*
+ * AVX512 is meant to be all or nothing, therefore for most of the kernel use
+ * the following definition.
+ */
+#define	XFEATURE_AVX512_OPMASK	(1 << 5)
+#define	XFEATURE_AVX512_ZMM	(1 << 6)
+#define	XFEATURE_AVX512_HI_ZMM	(1 << 7)
+#define	XFEATURE_AVX512		(XFEATURE_AVX512_OPMASK | \
+	XFEATURE_AVX512_ZMM | XFEATURE_AVX512_HI_ZMM)
 	/* bit 8 unused */
-#define	XFEATURE_PKRU		0x200
+#define	XFEATURE_PKRU		(1 << 9)
 #define	XFEATURE_FP_ALL	\
 	(XFEATURE_LEGACY_FP | XFEATURE_SSE | XFEATURE_AVX | XFEATURE_MPX | \
 	XFEATURE_AVX512 | XFEATURE_PKRU)
@@ -1576,8 +1590,9 @@
 extern uint_t cpuid_get_procnodes_per_pkg(struct cpu *cpu);
 extern uint_t cpuid_get_compunitid(struct cpu *cpu);
 extern uint_t cpuid_get_cores_per_compunit(struct cpu *cpu);
-extern size_t cpuid_get_xsave_size();
-extern boolean_t cpuid_need_fp_excp_handling();
+extern size_t cpuid_get_xsave_size(void);
+extern void cpuid_get_xsave_info(uint64_t, size_t *, size_t *);
+extern boolean_t cpuid_need_fp_excp_handling(void);
 extern int cpuid_is_cmt(struct cpu *);
 extern int cpuid_syscall32_insn(struct cpu *);
 extern int getl2cacheinfo(struct cpu *, int *, int *, int *);
--- old/usr/src/uts/intel/syscall/getcontext.c	Wed Apr 12 20:33:34 2023
+++ new/usr/src/uts/intel/syscall/getcontext.c	Wed Apr 12 20:33:34 2023
@@ -30,6 +30,10 @@
 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
 /*	  All Rights Reserved	*/
 
+/*
+ * Copyright 2023 Oxide Computer Company
+ */
+
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/vmparam.h>
@@ -52,22 +56,83 @@
 #include <sys/sdt.h>
 
 /*
+ * This is a wrapper around copyout_noerr that returns a guaranteed error code.
+ * Because we're using copyout_noerr(), we need to bound the time we're under an
+ * on_fault/no_fault and attempt to do so only while we're actually copying data
+ * out. The main reason for this is because we're being called back from the
+ * FPU, which is being held with a kpreempt_disable() and related, we can't use
+ * a larger on_fault()/no_fault() as that would both hide legitimate errors we
+ * make, masquerading as user issues, and it gets trickier to reason about the
+ * correct restoration of our state.
+ */
+static int
+savecontext_copyout(const void *kaddr, void *uaddr, size_t size)
+{
+	label_t ljb;
+	if (!on_fault(&ljb)) {
+		copyout_noerr(kaddr, uaddr, size);
+		no_fault();
+		return (0);
+	} else {
+		no_fault();
+		return (EFAULT);
+	}
+}
+
+/*
  * Save user context.
+ *
+ * Generally speaking ucp is a pointer to kernel memory. In the traditional
+ * version of this (when flags is 0), then we just write and fill out all of the
+ * ucontext_t without any care for what was there ahead of this. However, when
+ * we extended the state to push additional data when user pointers in the
+ * ucontext_t are valid (currently only uc_xsave), then we will copy out that
+ * extended state to the user pointer.
+ *
+ * We allow the copying to happen in two different ways mostly because this is
+ * also used in the signal handling context where we must be much more careful
+ * about how to copy out data.
  */
-void
-savecontext(ucontext_t *ucp, const k_sigset_t *mask)
+
+int
+savecontext(ucontext_t *ucp, const k_sigset_t *mask, savecontext_flags_t flags)
 {
 	proc_t *p = ttoproc(curthread);
 	klwp_t *lwp = ttolwp(curthread);
 	struct regs *rp = lwptoregs(lwp);
+	boolean_t need_xsave = B_FALSE;
+	boolean_t fpu_en;
+	long user_xsave = 0;
+	int ret;
 
+	VERIFY0(flags & ~(SAVECTXT_F_EXTD | SAVECTXT_F_ONFAULT));
+
 	/*
 	 * We unconditionally assign to every field through the end
 	 * of the gregs, but we need to bzero() everything -after- that
 	 * to avoid having any kernel stack garbage escape to userland.
+	 *
+	 * If we have been asked to save extended state, then we must make sure
+	 * that we don't clobber that value. We must also determine if the
+	 * processor has xsave state. If it does not, then we just simply honor
+	 * the pointer, but do not write anything out and do not set the flag.
 	 */
+	if ((flags & SAVECTXT_F_EXTD) != 0) {
+		user_xsave = ucp->uc_xsave;
+		if (fpu_xsave_enabled() && user_xsave != 0) {
+			need_xsave = B_TRUE;
+		}
+	} else {
+		/*
+		 * The only other flag that we have right now is about modifying
+		 * the copyout behavior when we're copying out extended
+		 * information. If it's not here, we should not do anything.
+		 */
+		VERIFY0(flags);
+	}
 	bzero(&ucp->uc_mcontext.fpregs, sizeof (ucontext_t) -
 	    offsetof(ucontext_t, uc_mcontext.fpregs));
+	ucp->uc_xsave = user_xsave;
 
 	ucp->uc_flags = UC_ALL;
 	ucp->uc_link = (struct ucontext *)lwp->lwp_oldcontext;
@@ -114,7 +179,7 @@
 			 * awakened it might see a pending signal and call
 			 * savecontext(), however on the way back to userland
 			 * there is no place it can be detected). Hence in
-			 * anticipation of such occassions, set AST flag for
+			 * anticipation of such occasions, set AST flag for
 			 * the thread which will make the thread take an
 			 * excursion through trap() where it will be handled
 			 * appropriately.
@@ -124,7 +189,8 @@
 	}
 
 	getgregs(lwp, ucp->uc_mcontext.gregs);
-	if (lwp->lwp_pcb.pcb_fpu.fpu_flags & FPU_EN)
+	fpu_en = (lwp->lwp_pcb.pcb_fpu.fpu_flags & FPU_EN) != 0;
+	if (fpu_en)
 		getfpregs(lwp, &ucp->uc_mcontext.fpregs);
 	else
 		ucp->uc_flags &= ~UC_FPU;
@@ -139,13 +205,43 @@
 		bzero(&ucp->uc_sigmask, sizeof (ucp->uc_sigmask));
 	}
 
+	/*
+	 * Determine if we need to get the reset of the xsave context out here.
+	 * If the thread doesn't actually have the FPU enabled, then we don't
+	 * actually need to do this. We also don't have to if it wasn't
+	 * requested.
+	 */
+	if (!need_xsave || !fpu_en) {
+		return (0);
+	}
+
+	ucp->uc_flags |= UC_XSAVE;
+
+	/*
+	 * While you might be asking why and contemplating despair, just know
+	 * that some things need to just be done in the face of signal (half the
+	 * reason this function exists). Basically when in signal context we
+	 * can't trigger watch points. This means we need to tell the FPU copy
+	 * logic to actually use the on_fault/no_fault and the non-error form of
+	 * copyout (which still checks if it's a user address at least).
+	 */
+	if ((flags & SAVECTXT_F_ONFAULT) != 0) {
+		ret = fpu_signal_copyout(lwp, ucp->uc_xsave,
+		    savecontext_copyout);
+	} else {
+		ret = fpu_signal_copyout(lwp, ucp->uc_xsave, copyout);
+	}
+
 	if (PROC_IS_BRANDED(p) && BROP(p)->b_savecontext != NULL) {
 		/*
 		 * Allow the brand the chance to modify the context we
 		 * saved:
 		 */
+		/* XXX KEBE SAYS FIX ME! */
 		BROP(p)->b_savecontext(ucp);
 	}
+
+	return (ret);
 }
 
 /*
@@ -193,8 +289,18 @@
 		aston(curthread);
 	}
 
-	if (ucp->uc_flags & UC_FPU)
+	/*
+	 * The logic to copy in the ucontex_t takes care of combining the UC_FPU
+	 * and UC_XSAVE, so at this point only one of them should be set, if
+	 * any.
+	 */
+	if (ucp->uc_flags & UC_XSAVE) {
+		ASSERT0(ucp->uc_flags & UC_FPU);
+		ASSERT3U((uintptr_t)ucp->uc_xsave, >=, _kernelbase);
+		fpu_set_xsave(lwp, (const void *)ucp->uc_xsave);
+	} else if (ucp->uc_flags & UC_FPU) {
 		setfpregs(lwp, &ucp->uc_mcontext.fpregs);
+	}
 
 	if (ucp->uc_flags & UC_SIGMASK) {
 		/*
@@ -215,8 +321,10 @@
 	ucontext_t uc;
 	ucontext_t *ucp;
 	klwp_t *lwp = ttolwp(curthread);
+	void *fpu = NULL;
 	stack_t dummy_stk;
 	proc_t *p = lwptoproc(lwp);
+	int ret;
 
 	/*
 	 * In future releases, when the ucontext structure grows,
@@ -232,7 +340,9 @@
 
 	case GETCONTEXT:
 		schedctl_finish_sigblock(curthread);
-		savecontext(&uc, &curthread->t_hold);
+		ret = savecontext(&uc, &curthread->t_hold, SAVECTXT_F_NONE);
+		if (ret != 0)
+			return (set_errno(ret));
 		if (uc.uc_flags & UC_SIGMASK)
 			SIGSET_NATIVE_TO_BRAND(&uc.uc_sigmask);
 		if (copyout(&uc, arg, sizeof (uc)))
@@ -239,6 +349,37 @@
 			return (set_errno(EFAULT));
 		return (0);
 
+	/*
+	 * In the case of GETCONTEXT_EXTD, we've theoretically been given all
+	 * the required pointers of the appropriate length by libc in the
+	 * ucontext_t. We must first copyin the offsets that we care about to
+	 * seed the known extensions. Right now that is just the uc_xsave
+	 * member. As we are setting uc_flags, we only look at the members we
+	 * need to care about.
+	 *
+	 * The main reason that we have a different entry point is that we don't
+	 * want to assume that callers have always properly zeroed their
+	 * ucontext_t ahead of calling into libc. In fact, it often is just
+	 * declared on the stack so we can't assume that at all. Instead,
+	 * getcontext_extd does require that.
+	 */
+	case GETCONTEXT_EXTD:
+		schedctl_finish_sigblock(curthread);
+		ucp = arg;
+		if (copyin(&ucp->uc_xsave, &uc.uc_xsave,
+		    sizeof (uc.uc_xsave)) != 0) {
+			return (set_errno(EFAULT));
+		}
+		ret = savecontext(&uc, &curthread->t_hold, SAVECTXT_F_EXTD);
+		if (ret != 0)
+			return (set_errno(ret));
+		if (uc.uc_flags & UC_SIGMASK)
+			SIGSET_NATIVE_TO_BRAND(&uc.uc_sigmask);
+		if (copyout(&uc, arg, sizeof (uc)))
+			return (set_errno(EFAULT));
+		return (0);
+
+
 	case SETCONTEXT:
 		ucp = arg;
 		if (ucp == NULL)
@@ -247,8 +388,7 @@
 		 * Don't copyin filler or floating state unless we need it.
 		 * The ucontext_t struct and fields are specified in the ABI.
 		 */
-		if (copyin(ucp, &uc, sizeof (ucontext_t) -
-		    sizeof (uc.uc_filler) -
+		if (copyin(ucp, &uc, offsetof(ucontext_t, uc_brand_data) -
 		    sizeof (uc.uc_mcontext.fpregs))) {
 			return (set_errno(EFAULT));
 		}
@@ -270,6 +410,21 @@
 			return (set_errno(EFAULT));
 		}
 
+		uc.uc_xsave = 0;
+		if ((uc.uc_flags & UC_XSAVE) != 0) {
+			int ret;
+
+			if (copyin(&ucp->uc_xsave, &uc.uc_xsave,
+			    sizeof (uc.uc_xsave)) != 0) {
+				return (set_errno(EFAULT));
+			}
+
+			ret = fpu_signal_copyin(lwp, &uc);
+			if (ret != 0) {
+				return (set_errno(ret));
+			}
+		}
+
 		restorecontext(&uc);
 
 		if ((uc.uc_flags & UC_STACK) && (lwp->lwp_ustack != 0))
@@ -295,15 +450,33 @@
 /*
  * Save user context for 32-bit processes.
  */
-void
-savecontext32(ucontext32_t *ucp, const k_sigset_t *mask)
+int
+savecontext32(ucontext32_t *ucp, const k_sigset_t *mask,
+    savecontext_flags_t flags)
 {
 	proc_t *p = ttoproc(curthread);
 	klwp_t *lwp = ttolwp(curthread);
 	struct regs *rp = lwptoregs(lwp);
+	boolean_t need_xsave = B_FALSE;
+	boolean_t fpu_en;
+	int32_t user_xsave = 0;
+	uintptr_t uaddr;
+	int ret;
 
+	/*
+	 * See savecontext for an explanation of this.
+	 */
+	if ((flags & SAVECTXT_F_EXTD) != 0) {
+		user_xsave = ucp->uc_xsave;
+		if (fpu_xsave_enabled() && user_xsave != 0) {
+			need_xsave = B_TRUE;
+		}
+	} else {
+		VERIFY0(flags);
+	}
 	bzero(&ucp->uc_mcontext.fpregs, sizeof (ucontext32_t) -
 	    offsetof(ucontext32_t, uc_mcontext.fpregs));
+	ucp->uc_xsave = user_xsave;
 
 	ucp->uc_flags = UC_ALL;
 	ucp->uc_link = (caddr32_t)lwp->lwp_oldcontext;
@@ -348,7 +521,8 @@
 	}
 
 	getgregs32(lwp, ucp->uc_mcontext.gregs);
-	if (lwp->lwp_pcb.pcb_fpu.fpu_flags & FPU_EN)
+	fpu_en = (lwp->lwp_pcb.pcb_fpu.fpu_flags & FPU_EN) != 0;
+	if (fpu_en)
 		getfpregs32(lwp, &ucp->uc_mcontext.fpregs);
 	else
 		ucp->uc_flags &= ~UC_FPU;
@@ -363,13 +537,38 @@
 		bzero(&ucp->uc_sigmask, sizeof (ucp->uc_sigmask));
 	}
 
+	if (!need_xsave || !fpu_en) {
+		return (0);
+	}
+
+	ucp->uc_flags |= UC_XSAVE;
+
+	/*
+	 * Due to not wanting to change or break programs, the filler in the
+	 * ucontext_t was always declared as a long, which is signed. Because
+	 * this is the 32-bit version, this is an int32_t. We cannot directly go
+	 * to a uintptr_t otherwise we might get sign extension, so we first
+	 * have to go through a uint32_t and then a uintptr_t. Otherwise, see
+	 * savecontext().
+	 */
+	uaddr = (uintptr_t)(uint32_t)ucp->uc_xsave;
+	if ((flags & SAVECTXT_F_ONFAULT) != 0) {
+		ret = fpu_signal_copyout(lwp, uaddr, savecontext_copyout);
+	} else {
+		ret = fpu_signal_copyout(lwp, uaddr, copyout);
+	}
+
+
 	if (PROC_IS_BRANDED(p) && BROP(p)->b_savecontext32 != NULL) {
 		/*
 		 * Allow the brand the chance to modify the context we
 		 * saved:
 		 */
+		/* XXX KEBE SAYS FIX ME */
 		BROP(p)->b_savecontext32(ucp);
 	}
+
+	return (ret);
 }
 
 int
@@ -382,6 +581,7 @@
 	caddr32_t ustack32;
 	stack32_t dummy_stk32;
 	proc_t *p = lwptoproc(lwp);
+	int ret;
 
 	switch (flag) {
 	default:
@@ -389,7 +589,9 @@
 
 	case GETCONTEXT:
 		schedctl_finish_sigblock(curthread);
-		savecontext32(&uc, &curthread->t_hold);
+		ret = savecontext32(&uc, &curthread->t_hold, SAVECTXT_F_NONE);
+		if (ret != 0)
+			return (set_errno(ret));
 		if (uc.uc_flags & UC_SIGMASK)
 			SIGSET_NATIVE_TO_BRAND(&uc.uc_sigmask);
 		if (copyout(&uc, arg, sizeof (uc)))
@@ -396,12 +598,30 @@
 			return (set_errno(EFAULT));
 		return (0);
 
+	/*
+	 * See getsetcontext() for an explanation of what is going on here.
+	 */
+	case GETCONTEXT_EXTD:
+		schedctl_finish_sigblock(curthread);
+		ucp = arg;
+		if (copyin(&ucp->uc_xsave, &uc.uc_xsave,
+		    sizeof (uc.uc_xsave)) != 0) {
+			return (set_errno(EFAULT));
+		}
+		ret = savecontext32(&uc, &curthread->t_hold, SAVECTXT_F_EXTD);
+		if (ret != 0)
+			return (set_errno(ret));
+		if (uc.uc_flags & UC_SIGMASK)
+			SIGSET_NATIVE_TO_BRAND(&uc.uc_sigmask);
+		if (copyout(&uc, arg, sizeof (uc)))
+			return (set_errno(EFAULT));
+		return (0);
+
 	case SETCONTEXT:
 		ucp = arg;
 		if (ucp == NULL)
 			exit(CLD_EXITED, 0);
-		if (copyin(ucp, &uc, sizeof (uc) -
-		    sizeof (uc.uc_filler) -
+		if (copyin(ucp, &uc, offsetof(ucontext32_t, uc_brand_data) -
 		    sizeof (uc.uc_mcontext.fpregs))) {
 			return (set_errno(EFAULT));
 		}
@@ -422,7 +642,22 @@
 			return (set_errno(EFAULT));
 		}
 
+		uc.uc_xsave = 0;
+		if ((uc.uc_flags & UC_XSAVE) != 0 &&
+		    copyin(&ucp->uc_xsave, &uc.uc_xsave,
+		    sizeof (uc.uc_xsave)) != 0) {
+			return (set_errno(EFAULT));
+		}
+
 		ucontext_32ton(&uc, &ucnat);
+
+		if ((ucnat.uc_flags & UC_XSAVE) != 0) {
+			int ret = fpu_signal_copyin(lwp, &ucnat);
+			if (ret != 0) {
+				return (set_errno(ret));
+			}
+		}
+
 		restorecontext(&ucnat);
 
 		if ((uc.uc_flags & UC_STACK) && (lwp->lwp_ustack != 0))
