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