Print this page
15254 %ymm registers not restored after signal handler
15367 x86 getfpregs() summons corrupting %xmm ghosts
15333 want x86 /proc xregs support (libc_db, libproc, mdb, etc.)
15336 want libc functions for extended ucontext_t
15334 want ps_lwphandle-specific reg routines
15328 FPU_CW_INIT mistreats reserved bit
15335 i86pc fpu_subr.c isn't really platform-specific
15332 setcontext(2) isn't actually noreturn
15331 need <sys/stdalign.h>
Change-Id: I7060aa86042dfb989f77fc3323c065ea2eafa9ad
Conflicts:
    usr/src/uts/common/fs/proc/prcontrol.c
    usr/src/uts/intel/os/archdep.c
    usr/src/uts/intel/sys/ucontext.h
    usr/src/uts/intel/syscall/getcontext.c
        
@@ -24,10 +24,11 @@
  */
 
 /*
  * Copyright 2018 Joyent, Inc.
  * Copyright (c) 2013 by Delphix. All rights reserved.
+ * Copyright 2023 Oxide Computer Company
  */
 
 #include <sys/types.h>
 #include <sys/uio.h>
 #include <string.h>
@@ -37,13 +38,19 @@
 #include "Pcontrol.h"
 #include "P32ton.h"
 
 /*
  * 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.
  */
 
 /*
  * Utility function to return a pointer to the structure of cached information
  * about an lwp in the core file, given its lwpid.
@@ -58,17 +65,18 @@
             lwp = list_next(&core->core_lwp_head, lwp)) {
                 if (lwp->lwp_id == lwpid)
                         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,
     const char *fbase, void *rp, size_t n)
 {
@@ -81,16 +89,75 @@
         if ((fd = open(fname, O_RDONLY)) >= 0) {
                 if (read(fd, rp, n) > 0) {
                         (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.
  */
 int
@@ -127,20 +194,53 @@
 
         return (-1);
 }
 
 /*
+ * 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;
                 return (-1);
         }
@@ -149,26 +249,36 @@
         iov[0].iov_len = sizeof (long);
         iov[1].iov_base = (caddr_t)rp;
         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);
         }
 
         /*
          * If the lwp we want is not the representative lwp, we need to
@@ -180,15 +290,44 @@
         if ((fd = open(fname, O_WRONLY)) >= 0) {
                 if (writev(fd, iov, 2) > 0) {
                         (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)
 {
         lwpstatus_t lps;
 
@@ -198,16 +337,29 @@
         (void) memcpy(gregs, lps.pr_reg, sizeof (prgregset_t));
         return (0);
 }
 
 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;
 
         if (getlwpstatus(P, lwpid, &lps) == -1)
@@ -215,20 +367,47 @@
 
         (void) memcpy(fpregs, &lps.pr_fpreg, sizeof (prfpregset_t));
         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) {
                 errno = ENODATA;
                 return (-1);
@@ -238,31 +417,73 @@
                 if (P->state != PS_STOP) {
                         errno = EBUSY;
                         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);
         }
 
         if (lwp != NULL)
                 errno = ENODATA;
         return (-1);
 }
 
 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;
 
         if (P->state == PS_IDLE) {
@@ -322,11 +543,11 @@
 }
 
 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 */
 
 int