/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2018 Joyent, Inc. * Copyright (c) 2013 by Delphix. All rights reserved. * Copyright 2023 Oxide Computer Company */ #include #include #include #include #include #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. 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. */ static lwp_info_t * getlwpcore(struct ps_prochandle *P, lwpid_t lwpid) { core_info_t *core = P->data; lwp_info_t *lwp; for (lwp = list_head(&core->core_lwp_head); lwp != NULL; lwp = list_next(&core->core_lwp_head, lwp)) { if (lwp->lwp_id == lwpid) return (lwp); } 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, lwpname, lwpsinfo, spymaster, * and others. */ static int getlwpfile(struct ps_prochandle *P, lwpid_t lwpid, const char *fbase, void *rp, size_t n) { 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) { 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 getlwpstatus(struct ps_prochandle *P, lwpid_t lwpid, lwpstatus_t *lps) { lwp_info_t *lwp; /* * For both live processes and cores, our job is easy if the lwpid * matches that of the representative lwp: */ if (P->status.pr_lwp.pr_lwpid == lwpid) { (void) memcpy(lps, &P->status.pr_lwp, sizeof (lwpstatus_t)); return (0); } /* * If this is a live process, then just read the information out * of the per-lwp status file: */ if (P->state != PS_DEAD) { return (getlwpfile(P, lwpid, "lwpstatus", lps, sizeof (lwpstatus_t))); } /* * If this is a core file, we need to iterate through our list of * cached lwp information and then copy out the status. */ if (P->data != NULL && (lwp = getlwpcore(P, lwpid)) != NULL) { (void) memcpy(lps, &lwp->lwp_status, sizeof (lwpstatus_t)); return (0); } 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. This assumes that * we have a process-level hold on things, which may not always be true. */ static int 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]; struct ps_lwphandle *L; int fd = -1; if (P->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; /* * 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. On some * platforms the xregs cover the base integer or floating point * registers. As a result, always refresh the representative LWP's * status. */ if (P->status.pr_lwp.pr_lwpid == lwpid) { Psync(P); fd = P->ctlfd; } if (fd > -1) { if (writev(fd, iov, 2) == -1) return (-1); refresh_status(P, lwpid, L, cmd, rp, n); return (0); } /* * If the lwp we want is not the representative lwp, we need to * open the ctl file for that specific lwp. */ (void) snprintf(fname, sizeof (fname), "%s/%d/lwp/%d/lwpctl", procfs_path, (int)P->status.pr_pid, (int)lwpid); 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; if (getlwpstatus(P, lwpid, &lps) == -1) return (-1); (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_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) return (-1); (void) memcpy(fpregs, &lps.pr_fpreg, sizeof (prfpregset_t)); return (0); } 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_proc(P, lwpid, PCSFPREG, fpregs, sizeof (prfpregset_t))); } int 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); } /* * Get a given thread's lwp registers. If this is a core file, we read it from * the cache. If this is a live process, we always read it from the underlying * file system because we do not currently cache xregs in libproc. sizep is the * resulting size of data we've allocated and for a live process is filled in * based on the /proc stat(2) information. */ 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); } if (P->state != PS_DEAD) { if (P->state != PS_STOP) { errno = EBUSY; return (-1); } return (getlwpfile_alloc(P, lwpid, "xregs", (void **)xregs, sizep)); } 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 Lgetxregs(struct ps_lwphandle *L, prxregset_t **xregs, size_t *sizep) { 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) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { if (P->state != PS_STOP) { errno = EBUSY; return (-1); } return (getlwpfile(P, lwpid, "gwindows", gwins, sizeof (gwindows_t))); } if ((lwp = getlwpcore(P, lwpid)) != NULL && lwp->lwp_gwins != NULL) { *gwins = *lwp->lwp_gwins; return (0); } if (lwp != NULL) errno = ENODATA; return (-1); } #if defined(__sparcv9) int Plwp_getasrs(struct ps_prochandle *P, lwpid_t lwpid, asrset_t asrs) { lwp_info_t *lwp; if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { if (P->state != PS_STOP) { errno = EBUSY; return (-1); } return (getlwpfile(P, lwpid, "asrs", asrs, sizeof (asrset_t))); } if ((lwp = getlwpcore(P, lwpid)) != NULL && lwp->lwp_asrs != NULL) { (void) memcpy(asrs, lwp->lwp_asrs, sizeof (asrset_t)); return (0); } if (lwp != NULL) errno = ENODATA; return (-1); } int Plwp_setasrs(struct ps_prochandle *P, lwpid_t lwpid, const asrset_t asrs) { return (setlwpregs_proc(P, lwpid, PCSASRS, asrs, sizeof (asrset_t))); } #endif /* __sparcv9 */ #endif /* __sparc */ int Plwp_getpsinfo(struct ps_prochandle *P, lwpid_t lwpid, lwpsinfo_t *lps) { lwp_info_t *lwp; if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { return (getlwpfile(P, lwpid, "lwpsinfo", lps, sizeof (lwpsinfo_t))); } if ((lwp = getlwpcore(P, lwpid)) != NULL) { (void) memcpy(lps, &lwp->lwp_psinfo, sizeof (lwpsinfo_t)); return (0); } return (-1); } int Plwp_getname(struct ps_prochandle *P, lwpid_t lwpid, char *buf, size_t bufsize) { char lwpname[THREAD_NAME_MAX]; char *from = NULL; lwp_info_t *lwp; if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { if (getlwpfile(P, lwpid, "lwpname", lwpname, sizeof (lwpname)) != 0) return (-1); from = lwpname; } else { if ((lwp = getlwpcore(P, lwpid)) == NULL) return (-1); from = lwp->lwp_name; } if (strlcpy(buf, from, bufsize) >= bufsize) { errno = ENAMETOOLONG; return (-1); } return (0); } int Plwp_getspymaster(struct ps_prochandle *P, lwpid_t lwpid, psinfo_t *ps) { lwpstatus_t lps; if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (getlwpstatus(P, lwpid, &lps) != 0) return (-1); if (!(lps.pr_flags & PR_AGENT)) { errno = EINVAL; return (-1); } if (P->state != PS_DEAD) { return (getlwpfile(P, lwpid, "spymaster", ps, sizeof (psinfo_t))); } if (P->spymaster.pr_nlwp != 0) { (void) memcpy(ps, &P->spymaster, sizeof (psinfo_t)); return (0); } errno = ENODATA; return (-1); } int Plwp_stack(struct ps_prochandle *P, lwpid_t lwpid, stack_t *stkp) { uintptr_t addr; if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { lwpstatus_t ls; if (getlwpfile(P, lwpid, "lwpstatus", &ls, sizeof (ls)) != 0) return (-1); addr = ls.pr_ustack; } else { lwp_info_t *lwp; if ((lwp = getlwpcore(P, lwpid)) == NULL) return (-1); addr = lwp->lwp_status.pr_ustack; } if (P->status.pr_dmodel == PR_MODEL_NATIVE) { if (Pread(P, stkp, sizeof (*stkp), addr) != sizeof (*stkp)) return (-1); #ifdef _LP64 } else { stack32_t stk32; if (Pread(P, &stk32, sizeof (stk32), addr) != sizeof (stk32)) return (-1); stack_32_to_n(&stk32, stkp); #endif } return (0); } int Plwp_main_stack(struct ps_prochandle *P, lwpid_t lwpid, stack_t *stkp) { uintptr_t addr; lwpstatus_t ls; if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { if (getlwpfile(P, lwpid, "lwpstatus", &ls, sizeof (ls)) != 0) return (-1); } else { lwp_info_t *lwp; if ((lwp = getlwpcore(P, lwpid)) == NULL) return (-1); ls = lwp->lwp_status; } addr = ls.pr_ustack; /* * Read out the current stack; if the SS_ONSTACK flag is set then * this LWP is operating on the alternate signal stack. We can * recover the original stack from pr_oldcontext. */ if (P->status.pr_dmodel == PR_MODEL_NATIVE) { if (Pread(P, stkp, sizeof (*stkp), addr) != sizeof (*stkp)) return (-1); if (stkp->ss_flags & SS_ONSTACK) goto on_altstack; #ifdef _LP64 } else { stack32_t stk32; if (Pread(P, &stk32, sizeof (stk32), addr) != sizeof (stk32)) return (-1); if (stk32.ss_flags & SS_ONSTACK) goto on_altstack; stack_32_to_n(&stk32, stkp); #endif } return (0); on_altstack: if (P->status.pr_dmodel == PR_MODEL_NATIVE) { ucontext_t *ctxp = (void *)ls.pr_oldcontext; if (Pread(P, stkp, sizeof (*stkp), (uintptr_t)&ctxp->uc_stack) != sizeof (*stkp)) return (-1); #ifdef _LP64 } else { ucontext32_t *ctxp = (void *)ls.pr_oldcontext; stack32_t stk32; if (Pread(P, &stk32, sizeof (stk32), (uintptr_t)&ctxp->uc_stack) != sizeof (stk32)) return (-1); stack_32_to_n(&stk32, stkp); #endif } return (0); } int Plwp_alt_stack(struct ps_prochandle *P, lwpid_t lwpid, stack_t *stkp) { if (P->state == PS_IDLE) { errno = ENODATA; return (-1); } if (P->state != PS_DEAD) { lwpstatus_t ls; if (getlwpfile(P, lwpid, "lwpstatus", &ls, sizeof (ls)) != 0) return (-1); if (ls.pr_altstack.ss_flags & SS_DISABLE) { errno = ENODATA; return (-1); } *stkp = ls.pr_altstack; } else { lwp_info_t *lwp; if ((lwp = getlwpcore(P, lwpid)) == NULL) return (-1); if (lwp->lwp_status.pr_altstack.ss_flags & SS_DISABLE) { errno = ENODATA; return (-1); } *stkp = lwp->lwp_status.pr_altstack; } return (0); }