--- 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 */
