diff options
Diffstat (limited to 'usr/src/lib/libproc/common/Pcontrol.c')
-rw-r--r-- | usr/src/lib/libproc/common/Pcontrol.c | 3693 |
1 files changed, 3693 insertions, 0 deletions
diff --git a/usr/src/lib/libproc/common/Pcontrol.c b/usr/src/lib/libproc/common/Pcontrol.c new file mode 100644 index 0000000000..a0db30858b --- /dev/null +++ b/usr/src/lib/libproc/common/Pcontrol.c @@ -0,0 +1,3693 @@ +/* + * 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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> +#include <string.h> +#include <memory.h> +#include <errno.h> +#include <dirent.h> +#include <limits.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <sys/param.h> +#include <sys/stack.h> +#include <sys/fault.h> +#include <sys/syscall.h> +#include <sys/sysmacros.h> + +#include "libproc.h" +#include "Pcontrol.h" +#include "Putil.h" +#include "P32ton.h" + +int _libproc_debug; /* set non-zero to enable debugging printfs */ +sigset_t blockable_sigs; /* signals to block when we need to be safe */ +static int minfd; /* minimum file descriptor returned by dupfd(fd, 0) */ + +/* + * Function prototypes for static routines in this module. + */ +static void deadcheck(struct ps_prochandle *); +static void restore_tracing_flags(struct ps_prochandle *); +static void Lfree_internal(struct ps_prochandle *, struct ps_lwphandle *); + +/* + * Read/write interface for live processes: just pread/pwrite the + * /proc/<pid>/as file: + */ + +static ssize_t +Pread_live(struct ps_prochandle *P, void *buf, size_t n, uintptr_t addr) +{ + return (pread(P->asfd, buf, n, (off_t)addr)); +} + +static ssize_t +Pwrite_live(struct ps_prochandle *P, const void *buf, size_t n, uintptr_t addr) +{ + return (pwrite(P->asfd, buf, n, (off_t)addr)); +} + +static const ps_rwops_t P_live_ops = { Pread_live, Pwrite_live }; + +/* + * This is the library's .init handler. + */ +#pragma init(_libproc_init) +void +_libproc_init(void) +{ + _libproc_debug = getenv("LIBPROC_DEBUG") != NULL; + + (void) sigfillset(&blockable_sigs); + (void) sigdelset(&blockable_sigs, SIGKILL); + (void) sigdelset(&blockable_sigs, SIGSTOP); +} + +/* + * Call set_minfd() once before calling dupfd() several times. + * We assume that the application will not reduce its current file + * descriptor limit lower than 512 once it has set at least that value. + */ +int +set_minfd(void) +{ + static mutex_t minfd_lock = DEFAULTMUTEX; + struct rlimit rlim; + int fd; + + if ((fd = minfd) < 256) { + (void) mutex_lock(&minfd_lock); + if ((fd = minfd) < 256) { + if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) + rlim.rlim_cur = rlim.rlim_max = 0; + if (rlim.rlim_cur >= 512) + fd = 256; + else if ((fd = rlim.rlim_cur / 2) < 3) + fd = 3; + minfd = fd; + } + (void) mutex_unlock(&minfd_lock); + } + return (fd); +} + +int +dupfd(int fd, int dfd) +{ + int mfd; + + /* + * Make fd be greater than 255 (the 32-bit stdio limit), + * or at least make it greater than 2 so that the + * program will work when spawned by init(1m). + * Also, if dfd is non-zero, dup the fd to be dfd. + */ + if ((mfd = minfd) == 0) + mfd = set_minfd(); + if (dfd > 0 || (0 <= fd && fd < mfd)) { + if (dfd <= 0) + dfd = mfd; + dfd = fcntl(fd, F_DUPFD, dfd); + (void) close(fd); + fd = dfd; + } + /* + * Mark it close-on-exec so any created process doesn't inherit it. + */ + if (fd >= 0) + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + return (fd); +} + +/* + * Create a new controlled process. + * Leave it stopped on successful exit from exec() or execve(). + * Return an opaque pointer to its process control structure. + * Return NULL if process cannot be created (fork()/exec() not successful). + */ +struct ps_prochandle * +Pxcreate(const char *file, /* executable file name */ + char *const *argv, /* argument vector */ + char *const *envp, /* environment */ + int *perr, /* pointer to error return code */ + char *path, /* if non-null, holds exec path name on return */ + size_t len) /* size of the path buffer */ +{ + char execpath[PATH_MAX]; + char procname[100]; + struct ps_prochandle *P; + pid_t pid; + int fd; + char *fname; + int rc; + int lasterrno = 0; + + if (len == 0) /* zero length, no path */ + path = NULL; + if (path != NULL) + *path = '\0'; + + if ((P = malloc(sizeof (struct ps_prochandle))) == NULL) { + *perr = C_STRANGE; + return (NULL); + } + + if ((pid = fork1()) == -1) { + free(P); + *perr = C_FORK; + return (NULL); + } + + if (pid == 0) { /* child process */ + id_t id; + extern char **environ; + + /* + * If running setuid or setgid, reset credentials to normal. + */ + if ((id = getgid()) != getegid()) + (void) setgid(id); + if ((id = getuid()) != geteuid()) + (void) setuid(id); + + Pcreate_callback(P); /* execute callback (see below) */ + (void) pause(); /* wait for PRSABORT from parent */ + + /* + * This is ugly. There is no execvep() function that takes a + * path and an environment. We cheat here by replacing the + * global 'environ' variable right before we call this. + */ + if (envp) + environ = (char **)envp; + + (void) execvp(file, argv); /* execute the program */ + _exit(127); + } + + /* + * Initialize the process structure. + */ + (void) memset(P, 0, sizeof (*P)); + (void) mutex_init(&P->proc_lock, USYNC_THREAD, NULL); + P->flags |= CREATED; + P->state = PS_RUN; + P->pid = pid; + P->asfd = -1; + P->ctlfd = -1; + P->statfd = -1; + P->agentctlfd = -1; + P->agentstatfd = -1; + P->ops = &P_live_ops; + Pinitsym(P); + + /* + * Open the /proc/pid files. + */ + (void) sprintf(procname, "/proc/%d/", (int)pid); + fname = procname + strlen(procname); + (void) set_minfd(); + + /* + * Exclusive write open advises others not to interfere. + * There is no reason for any of these open()s to fail. + */ + (void) strcpy(fname, "as"); + if ((fd = open(procname, (O_RDWR|O_EXCL))) < 0 || + (fd = dupfd(fd, 0)) < 0) { + dprintf("Pcreate: failed to open %s: %s\n", + procname, strerror(errno)); + rc = C_STRANGE; + goto bad; + } + P->asfd = fd; + + (void) strcpy(fname, "status"); + if ((fd = open(procname, O_RDONLY)) < 0 || + (fd = dupfd(fd, 0)) < 0) { + dprintf("Pcreate: failed to open %s: %s\n", + procname, strerror(errno)); + rc = C_STRANGE; + goto bad; + } + P->statfd = fd; + + (void) strcpy(fname, "ctl"); + if ((fd = open(procname, O_WRONLY)) < 0 || + (fd = dupfd(fd, 0)) < 0) { + dprintf("Pcreate: failed to open %s: %s\n", + procname, strerror(errno)); + rc = C_STRANGE; + goto bad; + } + P->ctlfd = fd; + + (void) Pstop(P, 0); /* stop the controlled process */ + + /* + * Wait for process to sleep in pause(). + * If the process has already called pause(), then it should be + * stopped (PR_REQUESTED) while asleep in pause and we are done. + * Else we set up to catch entry/exit to pause() and set the process + * running again, expecting it to stop when it reaches pause(). + * There is no reason for this to fail other than an interrupt. + */ + (void) Psysentry(P, SYS_pause, 1); + (void) Psysexit(P, SYS_pause, 1); + for (;;) { + if (P->state == PS_STOP && + P->status.pr_lwp.pr_syscall == SYS_pause && + (P->status.pr_lwp.pr_why == PR_REQUESTED || + P->status.pr_lwp.pr_why == PR_SYSENTRY || + P->status.pr_lwp.pr_why == PR_SYSEXIT)) + break; + + if (P->state != PS_STOP || /* interrupt or process died */ + Psetrun(P, 0, 0) != 0) { /* can't restart */ + if (errno == EINTR || errno == ERESTART) + rc = C_INTR; + else { + dprintf("Pcreate: Psetrun failed: %s\n", + strerror(errno)); + rc = C_STRANGE; + } + goto bad; + } + + (void) Pwait(P, 0); + } + (void) Psysentry(P, SYS_pause, 0); + (void) Psysexit(P, SYS_pause, 0); + + /* + * Kick the process off the pause() and catch + * it again on entry to exec() or exit(). + */ + (void) Psysentry(P, SYS_exit, 1); + (void) Psysentry(P, SYS_exec, 1); + (void) Psysentry(P, SYS_execve, 1); + if (Psetrun(P, 0, PRSABORT) == -1) { + dprintf("Pcreate: Psetrun failed: %s\n", strerror(errno)); + rc = C_STRANGE; + goto bad; + } + (void) Pwait(P, 0); + if (P->state != PS_STOP) { + dprintf("Pcreate: Pwait failed: %s\n", strerror(errno)); + rc = C_STRANGE; + goto bad; + } + + /* + * Move the process through instances of failed exec()s + * to reach the point of stopped on successful exec(). + */ + (void) Psysexit(P, SYS_exec, TRUE); + (void) Psysexit(P, SYS_execve, TRUE); + + while (P->state == PS_STOP && + P->status.pr_lwp.pr_why == PR_SYSENTRY && + (P->status.pr_lwp.pr_what == SYS_execve || + P->status.pr_lwp.pr_what == SYS_exec)) { + /* + * Fetch the exec path name now, before we complete + * the exec(). We may lose the process and be unable + * to get the information later. + */ + (void) Pread_string(P, execpath, sizeof (execpath), + (off_t)P->status.pr_lwp.pr_sysarg[0]); + if (path != NULL) + (void) strncpy(path, execpath, len); + /* + * Set the process running and wait for + * it to stop on exit from the exec(). + */ + (void) Psetrun(P, 0, 0); + (void) Pwait(P, 0); + + if (P->state == PS_LOST && /* we lost control */ + Preopen(P) != 0) { /* and we can't get it back */ + rc = C_PERM; + goto bad; + } + + /* + * If the exec() failed, continue the loop, expecting + * there to be more attempts to exec(), based on PATH. + */ + if (P->state == PS_STOP && + P->status.pr_lwp.pr_why == PR_SYSEXIT && + (P->status.pr_lwp.pr_what == SYS_execve || + P->status.pr_lwp.pr_what == SYS_exec) && + (lasterrno = P->status.pr_lwp.pr_errno) != 0) { + /* + * The exec() failed. Set the process running and + * wait for it to stop on entry to the next exec(). + */ + (void) Psetrun(P, 0, 0); + (void) Pwait(P, 0); + + continue; + } + break; + } + + if (P->state == PS_STOP && + P->status.pr_lwp.pr_why == PR_SYSEXIT && + (P->status.pr_lwp.pr_what == SYS_execve || + P->status.pr_lwp.pr_what == SYS_exec) && + P->status.pr_lwp.pr_errno == 0) { + /* + * The process is stopped on successful exec() or execve(). + * Turn off all tracing flags and return success. + */ + restore_tracing_flags(P); +#ifndef _LP64 + /* We must be a 64-bit process to deal with a 64-bit process */ + if (P->status.pr_dmodel == PR_MODEL_LP64) { + rc = C_LP64; + goto bad; + } +#endif + /* + * Set run-on-last-close so the controlled process + * runs even if we die on a signal. + */ + (void) Psetflags(P, PR_RLC); + *perr = 0; + return (P); + } + + rc = lasterrno == ENOENT ? C_NOENT : C_NOEXEC; + +bad: + (void) kill(pid, SIGKILL); + if (path != NULL && rc != C_PERM && rc != C_LP64) + *path = '\0'; + Pfree(P); + *perr = rc; + return (NULL); +} + +struct ps_prochandle * +Pcreate( + const char *file, /* executable file name */ + char *const *argv, /* argument vector */ + int *perr, /* pointer to error return code */ + char *path, /* if non-null, holds exec path name on return */ + size_t len) /* size of the path buffer */ +{ + return (Pxcreate(file, argv, NULL, perr, path, len)); +} + +/* + * Return a printable string corresponding to a Pcreate() error return. + */ +const char * +Pcreate_error(int error) +{ + const char *str; + + switch (error) { + case C_FORK: + str = "cannot fork"; + break; + case C_PERM: + str = "file is set-id or unreadable"; + break; + case C_NOEXEC: + str = "cannot execute file"; + break; + case C_INTR: + str = "operation interrupted"; + break; + case C_LP64: + str = "program is _LP64, self is not"; + break; + case C_STRANGE: + str = "unanticipated system error"; + break; + case C_NOENT: + str = "cannot find executable file"; + break; + default: + str = "unknown error"; + break; + } + + return (str); +} + +/* + * Callback to execute in each child process created with Pcreate() after fork + * but before it execs the new process image. By default, we do nothing, but + * by calling this function we allow the client program to define its own + * version of the function which will interpose on our empty default. This + * may be useful for clients that need to modify signal dispositions, terminal + * attributes, or process group and session properties for each new victim. + */ +/*ARGSUSED*/ +void +Pcreate_callback(struct ps_prochandle *P) +{ + /* nothing to do here */ +} + +/* + * Grab an existing process. + * Return an opaque pointer to its process control structure. + * + * pid: UNIX process ID. + * flags: + * PGRAB_RETAIN Retain tracing flags (default clears all tracing flags). + * PGRAB_FORCE Grab regardless of whether process is already traced. + * PGRAB_RDONLY Open the address space file O_RDONLY instead of O_RDWR, + * and do not open the process control file. + * PGRAB_NOSTOP Open the process but do not force it to stop. + * perr: pointer to error return code. + */ +struct ps_prochandle * +Pgrab(pid_t pid, int flags, int *perr) +{ + struct ps_prochandle *P; + int fd, omode; + char procname[100]; + char *fname; + int rc = 0; + + /* + * PGRAB_RDONLY means that we do not open the /proc/<pid>/control file, + * and so it implies RETAIN and NOSTOP since both require control. + */ + if (flags & PGRAB_RDONLY) + flags |= PGRAB_RETAIN | PGRAB_NOSTOP; + + if ((P = malloc(sizeof (struct ps_prochandle))) == NULL) { + *perr = G_STRANGE; + return (NULL); + } + + P->asfd = -1; + P->ctlfd = -1; + P->statfd = -1; + +again: /* Come back here if we lose it in the Window of Vulnerability */ + if (P->ctlfd >= 0) + (void) close(P->ctlfd); + if (P->asfd >= 0) + (void) close(P->asfd); + if (P->statfd >= 0) + (void) close(P->statfd); + (void) memset(P, 0, sizeof (*P)); + (void) mutex_init(&P->proc_lock, USYNC_THREAD, NULL); + P->ctlfd = -1; + P->asfd = -1; + P->statfd = -1; + P->agentctlfd = -1; + P->agentstatfd = -1; + P->ops = &P_live_ops; + Pinitsym(P); + + /* + * Open the /proc/pid files + */ + (void) sprintf(procname, "/proc/%d/", (int)pid); + fname = procname + strlen(procname); + (void) set_minfd(); + + /* + * Request exclusive open to avoid grabbing someone else's + * process and to prevent others from interfering afterwards. + * If this fails and the 'PGRAB_FORCE' flag is set, attempt to + * open non-exclusively. + */ + (void) strcpy(fname, "as"); + omode = (flags & PGRAB_RDONLY) ? O_RDONLY : O_RDWR; + + if (((fd = open(procname, omode | O_EXCL)) < 0 && + (fd = ((flags & PGRAB_FORCE)? open(procname, omode) : -1)) < 0) || + (fd = dupfd(fd, 0)) < 0) { + switch (errno) { + case ENOENT: + rc = G_NOPROC; + break; + case EACCES: + case EPERM: + rc = G_PERM; + break; + case EBUSY: + if (!(flags & PGRAB_FORCE) || geteuid() != 0) { + rc = G_BUSY; + break; + } + /* FALLTHROUGH */ + default: + dprintf("Pgrab: failed to open %s: %s\n", + procname, strerror(errno)); + rc = G_STRANGE; + break; + } + goto err; + } + P->asfd = fd; + + (void) strcpy(fname, "status"); + if ((fd = open(procname, O_RDONLY)) < 0 || + (fd = dupfd(fd, 0)) < 0) { + switch (errno) { + case ENOENT: + rc = G_NOPROC; + break; + default: + dprintf("Pgrab: failed to open %s: %s\n", + procname, strerror(errno)); + rc = G_STRANGE; + break; + } + goto err; + } + P->statfd = fd; + + if (!(flags & PGRAB_RDONLY)) { + (void) strcpy(fname, "ctl"); + if ((fd = open(procname, O_WRONLY)) < 0 || + (fd = dupfd(fd, 0)) < 0) { + switch (errno) { + case ENOENT: + rc = G_NOPROC; + break; + default: + dprintf("Pgrab: failed to open %s: %s\n", + procname, strerror(errno)); + rc = G_STRANGE; + break; + } + goto err; + } + P->ctlfd = fd; + } + + P->state = PS_RUN; + P->pid = pid; + + /* + * We are now in the Window of Vulnerability (WoV). The process may + * exec() a setuid/setgid or unreadable object file between the open() + * and the PCSTOP. We will get EAGAIN in this case and must start over. + * As Pstopstatus will trigger the first read() from a /proc file, + * we also need to handle EOVERFLOW here when 32-bit as an indicator + * that this process is 64-bit. Finally, if the process has become + * a zombie (PS_UNDEAD) while we were trying to grab it, just remain + * silent about this and pretend there was no process. + */ + if (Pstopstatus(P, PCNULL, 0) != 0) { +#ifndef _LP64 + if (errno == EOVERFLOW) { + rc = G_LP64; + goto err; + } +#endif + if (P->state == PS_LOST) { /* WoV */ + (void) mutex_destroy(&P->proc_lock); + goto again; + } + + if (P->state == PS_UNDEAD) + rc = G_NOPROC; + else + rc = G_STRANGE; + + goto err; + } + + /* + * If the process is a system process, we can't control it even as root + */ + if (P->status.pr_flags & PR_ISSYS) { + rc = G_SYS; + goto err; + } +#ifndef _LP64 + /* + * We must be a 64-bit process to deal with a 64-bit process + */ + if (P->status.pr_dmodel == PR_MODEL_LP64) { + rc = G_LP64; + goto err; + } +#endif + + /* + * Remember the status for use by Prelease(). + */ + P->orig_status = P->status; /* structure copy */ + + /* + * Before stopping the process, make sure we are not grabbing ourselves. + * If we are, make sure we are doing it PGRAB_RDONLY. + */ + if (pid == getpid()) { + /* + * Verify that the process is really ourself: + * Set a magic number, read it through the + * /proc file and see if the results match. + */ + uint32_t magic1 = 0; + uint32_t magic2 = 2; + + errno = 0; + + if (Pread(P, &magic2, sizeof (magic2), (uintptr_t)&magic1) + == sizeof (magic2) && + magic2 == 0 && + (magic1 = 0xfeedbeef) && + Pread(P, &magic2, sizeof (magic2), (uintptr_t)&magic1) + == sizeof (magic2) && + magic2 == 0xfeedbeef && + !(flags & PGRAB_RDONLY)) { + rc = G_SELF; + goto err; + } + } + + /* + * If the process is already stopped or has been directed + * to stop via /proc, do not set run-on-last-close. + */ + if (!(P->status.pr_lwp.pr_flags & (PR_ISTOP|PR_DSTOP)) && + !(flags & PGRAB_RDONLY)) { + /* + * Mark the process run-on-last-close so + * it runs even if we die from SIGKILL. + */ + if (Psetflags(P, PR_RLC) != 0) { + if (errno == EAGAIN) { /* WoV */ + (void) mutex_destroy(&P->proc_lock); + goto again; + } + if (errno == ENOENT) /* No complaint about zombies */ + rc = G_ZOMB; + else { + dprintf("Pgrab: failed to set RLC\n"); + rc = G_STRANGE; + } + goto err; + } + } + + /* + * If a stop directive is pending and the process has not yet stopped, + * then synchronously wait for the stop directive to take effect. + * Limit the time spent waiting for the process to stop by iterating + * at most 10 times. The time-out of 20 ms corresponds to the time + * between sending the stop directive and the process actually stopped + * as measured by DTrace on a slow, busy system. If the process doesn't + * stop voluntarily, clear the PR_DSTOP flag so that the code below + * forces the process to stop. + */ + if (!(flags & PGRAB_RDONLY)) { + int niter = 0; + while ((P->status.pr_lwp.pr_flags & (PR_STOPPED|PR_DSTOP)) == + PR_DSTOP && niter < 10 && + Pstopstatus(P, PCTWSTOP, 20) != 0) { + niter++; + if (flags & PGRAB_NOSTOP) + break; + } + if (niter == 10 && !(flags & PGRAB_NOSTOP)) { + /* Try it harder down below */ + P->status.pr_lwp.pr_flags &= ~PR_DSTOP; + } + } + + /* + * If the process is not already stopped or directed to stop + * and PGRAB_NOSTOP was not specified, stop the process now. + */ + if (!(P->status.pr_lwp.pr_flags & (PR_ISTOP|PR_DSTOP)) && + !(flags & PGRAB_NOSTOP)) { + /* + * Stop the process, get its status and signal/syscall masks. + */ + if (((P->status.pr_lwp.pr_flags & PR_STOPPED) && + Pstopstatus(P, PCDSTOP, 0) != 0) || + Pstopstatus(P, PCSTOP, 2000) != 0) { +#ifndef _LP64 + if (errno == EOVERFLOW) { + rc = G_LP64; + goto err; + } +#endif + if (P->state == PS_LOST) { /* WoV */ + (void) mutex_destroy(&P->proc_lock); + goto again; + } + if ((errno != EINTR && errno != ERESTART) || + (P->state != PS_STOP && + !(P->status.pr_flags & PR_DSTOP))) { + if (P->state != PS_RUN && errno != ENOENT) { + dprintf("Pgrab: failed to PCSTOP\n"); + rc = G_STRANGE; + } else { + rc = G_ZOMB; + } + goto err; + } + } + + /* + * Process should now either be stopped via /proc or there + * should be an outstanding stop directive. + */ + if (!(P->status.pr_flags & (PR_ISTOP|PR_DSTOP))) { + dprintf("Pgrab: process is not stopped\n"); + rc = G_STRANGE; + goto err; + } +#ifndef _LP64 + /* + * Test this again now because the 32-bit victim process may + * have exec'd a 64-bit process in the meantime. + */ + if (P->status.pr_dmodel == PR_MODEL_LP64) { + rc = G_LP64; + goto err; + } +#endif + } + + /* + * Cancel all tracing flags unless the PGRAB_RETAIN flag is set. + */ + if (!(flags & PGRAB_RETAIN)) { + (void) Psysentry(P, 0, FALSE); + (void) Psysexit(P, 0, FALSE); + (void) Psignal(P, 0, FALSE); + (void) Pfault(P, 0, FALSE); + Psync(P); + } + + *perr = 0; + return (P); + +err: + Pfree(P); + *perr = rc; + return (NULL); +} + +/* + * Return a printable string corresponding to a Pgrab() error return. + */ +const char * +Pgrab_error(int error) +{ + const char *str; + + switch (error) { + case G_NOPROC: + str = "no such process"; + break; + case G_NOCORE: + str = "no such core file"; + break; + case G_NOPROCORCORE: + str = "no such process or core file"; + break; + case G_NOEXEC: + str = "cannot find executable file"; + break; + case G_ZOMB: + str = "zombie process"; + break; + case G_PERM: + str = "permission denied"; + break; + case G_BUSY: + str = "process is traced"; + break; + case G_SYS: + str = "system process"; + break; + case G_SELF: + str = "attempt to grab self"; + break; + case G_INTR: + str = "operation interrupted"; + break; + case G_LP64: + str = "program is _LP64, self is not"; + break; + case G_FORMAT: + str = "file is not an ELF core file"; + break; + case G_ELF: + str = "libelf error"; + break; + case G_NOTE: + str = "core file is corrupt or missing required data"; + break; + case G_STRANGE: + str = "unanticipated system error"; + break; + case G_ISAINVAL: + str = "wrong ELF machine type"; + break; + case G_BADLWPS: + str = "bad lwp specification"; + break; + default: + str = "unknown error"; + break; + } + + return (str); +} + +/* + * Free a process control structure. + * Close the file descriptors but don't do the Prelease logic. + */ +void +Pfree(struct ps_prochandle *P) +{ + uint_t i; + + if (P->core != NULL) { + extern void __priv_free_info(void *); + lwp_info_t *nlwp, *lwp = list_next(&P->core->core_lwp_head); + + for (i = 0; i < P->core->core_nlwp; i++, lwp = nlwp) { + nlwp = list_next(lwp); +#ifdef __sparc + if (lwp->lwp_gwins != NULL) + free(lwp->lwp_gwins); + if (lwp->lwp_xregs != NULL) + free(lwp->lwp_xregs); + if (lwp->lwp_asrs != NULL) + free(lwp->lwp_asrs); +#endif + free(lwp); + } + + if (P->core->core_platform != NULL) + free(P->core->core_platform); + if (P->core->core_uts != NULL) + free(P->core->core_uts); + if (P->core->core_cred != NULL) + free(P->core->core_cred); + if (P->core->core_priv != NULL) + free(P->core->core_priv); + if (P->core->core_privinfo != NULL) + __priv_free_info(P->core->core_privinfo); + if (P->core->core_ppii != NULL) + free(P->core->core_ppii); + if (P->core->core_zonename != NULL) + free(P->core->core_zonename); +#if defined(__i386) || defined(__amd64) + if (P->core->core_ldt != NULL) + free(P->core->core_ldt); +#endif + + free(P->core); + } + + if (P->ucaddrs != NULL) { + free(P->ucaddrs); + P->ucaddrs = NULL; + P->ucnelems = 0; + } + + (void) mutex_lock(&P->proc_lock); + if (P->hashtab != NULL) { + struct ps_lwphandle *L; + for (i = 0; i < HASHSIZE; i++) { + while ((L = P->hashtab[i]) != NULL) + Lfree_internal(P, L); + } + free(P->hashtab); + } + (void) mutex_unlock(&P->proc_lock); + (void) mutex_destroy(&P->proc_lock); + + if (P->agentctlfd >= 0) + (void) close(P->agentctlfd); + if (P->agentstatfd >= 0) + (void) close(P->agentstatfd); + if (P->ctlfd >= 0) + (void) close(P->ctlfd); + if (P->asfd >= 0) + (void) close(P->asfd); + if (P->statfd >= 0) + (void) close(P->statfd); + Preset_maps(P); + + /* clear out the structure as a precaution against reuse */ + (void) memset(P, 0, sizeof (*P)); + P->ctlfd = -1; + P->asfd = -1; + P->statfd = -1; + P->agentctlfd = -1; + P->agentstatfd = -1; + + free(P); +} + +/* + * Return the state of the process, one of the PS_* values. + */ +int +Pstate(struct ps_prochandle *P) +{ + return (P->state); +} + +/* + * Return the open address space file descriptor for the process. + * Clients must not close this file descriptor, not use it + * after the process is freed. + */ +int +Pasfd(struct ps_prochandle *P) +{ + return (P->asfd); +} + +/* + * Return the open control file descriptor for the process. + * Clients must not close this file descriptor, not use it + * after the process is freed. + */ +int +Pctlfd(struct ps_prochandle *P) +{ + return (P->ctlfd); +} + +/* + * Return a pointer to the process psinfo structure. + * Clients should not hold on to this pointer indefinitely. + * It will become invalid on Prelease(). + */ +const psinfo_t * +Ppsinfo(struct ps_prochandle *P) +{ + if (P->state == PS_IDLE) { + errno = ENODATA; + return (NULL); + } + + if (P->state != PS_DEAD && proc_get_psinfo(P->pid, &P->psinfo) == -1) + return (NULL); + + return (&P->psinfo); +} + +/* + * Return a pointer to the process status structure. + * Clients should not hold on to this pointer indefinitely. + * It will become invalid on Prelease(). + */ +const pstatus_t * +Pstatus(struct ps_prochandle *P) +{ + return (&P->status); +} + +/* + * Fill in a pointer to a process credentials structure. The ngroups parameter + * is the number of supplementary group entries allocated in the caller's cred + * structure. It should equal zero or one unless extra space has been + * allocated for the group list by the caller. + */ +int +Pcred(struct ps_prochandle *P, prcred_t *pcrp, int ngroups) +{ + if (P->state == PS_IDLE) { + errno = ENODATA; + return (-1); + } + + if (P->state != PS_DEAD) + return (proc_get_cred(P->pid, pcrp, ngroups)); + + if (P->core->core_cred != NULL) { + /* + * Avoid returning more supplementary group data than the + * caller has allocated in their buffer. We expect them to + * check pr_ngroups afterward and potentially call us again. + */ + ngroups = MIN(ngroups, P->core->core_cred->pr_ngroups); + + (void) memcpy(pcrp, P->core->core_cred, + sizeof (prcred_t) + (ngroups - 1) * sizeof (gid_t)); + + return (0); + } + + errno = ENODATA; + return (-1); +} + +#if defined(__i386) || defined(__amd64) +/* + * Fill in a pointer to a process LDT structure. + * The caller provides a buffer of size 'nldt * sizeof (struct ssd)'; + * If pldt == NULL or nldt == 0, we return the number of existing LDT entries. + * Otherwise we return the actual number of LDT entries fetched (<= nldt). + */ +int +Pldt(struct ps_prochandle *P, struct ssd *pldt, int nldt) +{ + if (P->state == PS_IDLE) { + errno = ENODATA; + return (-1); + } + + if (P->state != PS_DEAD) + return (proc_get_ldt(P->pid, pldt, nldt)); + + if (pldt == NULL || nldt == 0) + return (P->core->core_nldt); + + if (P->core->core_ldt != NULL) { + nldt = MIN(nldt, P->core->core_nldt); + + (void) memcpy(pldt, P->core->core_ldt, + nldt * sizeof (struct ssd)); + + return (nldt); + } + + errno = ENODATA; + return (-1); +} +#endif /* __i386 */ + +/* + * Fill in a pointer to a process privilege structure. + */ +ssize_t +Ppriv(struct ps_prochandle *P, prpriv_t *pprv, size_t size) +{ + if (P->state != PS_DEAD) { + prpriv_t *pp = proc_get_priv(P->pid); + if (pp != NULL) { + size = MIN(size, PRIV_PRPRIV_SIZE(pp)); + (void) memcpy(pprv, pp, size); + free(pp); + return (size); + } + return (-1); + } + + if (P->core->core_priv != NULL) { + size = MIN(P->core->core_priv_size, size); + (void) memcpy(pprv, P->core->core_priv, size); + return (size); + } + errno = ENODATA; + return (-1); +} + +int +Psetpriv(struct ps_prochandle *P, prpriv_t *pprv) +{ + int rc; + long *ctl; + size_t sz; + + if (P->state == PS_DEAD) { + errno = EBADF; + return (-1); + } + + sz = PRIV_PRPRIV_SIZE(pprv) + sizeof (long); + + sz = ((sz - 1) / sizeof (long) + 1) * sizeof (long); + + ctl = malloc(sz); + if (ctl == NULL) + return (-1); + + ctl[0] = PCSPRIV; + + (void) memcpy(&ctl[1], pprv, PRIV_PRPRIV_SIZE(pprv)); + + if (write(P->ctlfd, ctl, sz) != sz) + rc = -1; + else + rc = 0; + + free(ctl); + + return (rc); +} + +void * +Pprivinfo(struct ps_prochandle *P) +{ + /* Use default from libc */ + if (P->state != PS_DEAD) + return (NULL); + + return (P->core->core_privinfo); +} + +/* + * Ensure that all cached state is written to the process. + * The cached state is the LWP's signal mask and registers + * and the process's tracing flags. + */ +void +Psync(struct ps_prochandle *P) +{ + int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd; + long cmd[6]; + iovec_t iov[12]; + int n = 0; + + if (P->flags & SETHOLD) { + cmd[0] = PCSHOLD; + iov[n].iov_base = (caddr_t)&cmd[0]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&P->status.pr_lwp.pr_lwphold; + iov[n++].iov_len = sizeof (P->status.pr_lwp.pr_lwphold); + } + if (P->flags & SETREGS) { + cmd[1] = PCSREG; +#ifdef __i386 + /* XX64 we should probably restore REG_GS after this */ + if (ctlfd == P->agentctlfd) + P->status.pr_lwp.pr_reg[GS] = 0; +#elif defined(__amd64) + /* XX64 */ +#endif + iov[n].iov_base = (caddr_t)&cmd[1]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&P->status.pr_lwp.pr_reg[0]; + iov[n++].iov_len = sizeof (P->status.pr_lwp.pr_reg); + } + if (P->flags & SETSIG) { + cmd[2] = PCSTRACE; + iov[n].iov_base = (caddr_t)&cmd[2]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&P->status.pr_sigtrace; + iov[n++].iov_len = sizeof (P->status.pr_sigtrace); + } + if (P->flags & SETFAULT) { + cmd[3] = PCSFAULT; + iov[n].iov_base = (caddr_t)&cmd[3]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&P->status.pr_flttrace; + iov[n++].iov_len = sizeof (P->status.pr_flttrace); + } + if (P->flags & SETENTRY) { + cmd[4] = PCSENTRY; + iov[n].iov_base = (caddr_t)&cmd[4]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&P->status.pr_sysentry; + iov[n++].iov_len = sizeof (P->status.pr_sysentry); + } + if (P->flags & SETEXIT) { + cmd[5] = PCSEXIT; + iov[n].iov_base = (caddr_t)&cmd[5]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&P->status.pr_sysexit; + iov[n++].iov_len = sizeof (P->status.pr_sysexit); + } + + if (n == 0 || writev(ctlfd, iov, n) < 0) + return; /* nothing to do or write failed */ + + P->flags &= ~(SETSIG|SETFAULT|SETENTRY|SETEXIT|SETHOLD|SETREGS); +} + +/* + * Reopen the /proc file (after PS_LOST). + */ +int +Preopen(struct ps_prochandle *P) +{ + int fd; + char procname[100]; + char *fname; + + if (P->state == PS_DEAD || P->state == PS_IDLE) + return (0); + + if (P->agentcnt > 0) { + P->agentcnt = 1; + Pdestroy_agent(P); + } + + (void) sprintf(procname, "/proc/%d/", (int)P->pid); + fname = procname + strlen(procname); + + (void) strcpy(fname, "as"); + if ((fd = open(procname, O_RDWR)) < 0 || + close(P->asfd) < 0 || + (fd = dupfd(fd, P->asfd)) != P->asfd) { + dprintf("Preopen: failed to open %s: %s\n", + procname, strerror(errno)); + if (fd >= 0) + (void) close(fd); + return (-1); + } + P->asfd = fd; + + (void) strcpy(fname, "status"); + if ((fd = open(procname, O_RDONLY)) < 0 || + close(P->statfd) < 0 || + (fd = dupfd(fd, P->statfd)) != P->statfd) { + dprintf("Preopen: failed to open %s: %s\n", + procname, strerror(errno)); + if (fd >= 0) + (void) close(fd); + return (-1); + } + P->statfd = fd; + + (void) strcpy(fname, "ctl"); + if ((fd = open(procname, O_WRONLY)) < 0 || + close(P->ctlfd) < 0 || + (fd = dupfd(fd, P->ctlfd)) != P->ctlfd) { + dprintf("Preopen: failed to open %s: %s\n", + procname, strerror(errno)); + if (fd >= 0) + (void) close(fd); + return (-1); + } + P->ctlfd = fd; + + /* + * Set the state to PS_RUN and wait for the process to stop so that + * we re-read the status from the new P->statfd. If this fails, Pwait + * will reset the state to PS_LOST and we fail the reopen. Before + * returning, we also forge a bit of P->status to allow the debugger to + * see that we are PS_LOST following a successful exec. + */ + P->state = PS_RUN; + if (Pwait(P, 0) == -1) { +#ifdef _ILP32 + if (errno == EOVERFLOW) + P->status.pr_dmodel = PR_MODEL_LP64; +#endif + P->status.pr_lwp.pr_why = PR_SYSEXIT; + P->status.pr_lwp.pr_what = SYS_execve; + P->status.pr_lwp.pr_errno = 0; + return (-1); + } + + /* + * The process should be stopped on exec (REQUESTED) + * or else should be stopped on exit from exec() (SYSEXIT) + */ + if (P->state == PS_STOP && + (P->status.pr_lwp.pr_why == PR_REQUESTED || + (P->status.pr_lwp.pr_why == PR_SYSEXIT && + (P->status.pr_lwp.pr_what == SYS_exec || + P->status.pr_lwp.pr_what == SYS_execve)))) { + /* fake up stop-on-exit-from-execve */ + if (P->status.pr_lwp.pr_why == PR_REQUESTED) { + P->status.pr_lwp.pr_why = PR_SYSEXIT; + P->status.pr_lwp.pr_what = SYS_execve; + P->status.pr_lwp.pr_errno = 0; + } + } else { + dprintf("Preopen: expected REQUESTED or " + "SYSEXIT(SYS_execve) stop\n"); + } + + return (0); +} + +/* + * Define all settable flags other than the microstate accounting flags. + */ +#define ALL_SETTABLE_FLAGS (PR_FORK|PR_RLC|PR_KLC|PR_ASYNC|PR_BPTADJ|PR_PTRACE) + +/* + * Restore /proc tracing flags to their original values + * in preparation for releasing the process. + * Also called by Pcreate() to clear all tracing flags. + */ +static void +restore_tracing_flags(struct ps_prochandle *P) +{ + long flags; + long cmd[4]; + iovec_t iov[8]; + + if (P->flags & CREATED) { + /* we created this process; clear all tracing flags */ + premptyset(&P->status.pr_sigtrace); + premptyset(&P->status.pr_flttrace); + premptyset(&P->status.pr_sysentry); + premptyset(&P->status.pr_sysexit); + if ((P->status.pr_flags & ALL_SETTABLE_FLAGS) != 0) + (void) Punsetflags(P, ALL_SETTABLE_FLAGS); + } else { + /* we grabbed the process; restore its tracing flags */ + P->status.pr_sigtrace = P->orig_status.pr_sigtrace; + P->status.pr_flttrace = P->orig_status.pr_flttrace; + P->status.pr_sysentry = P->orig_status.pr_sysentry; + P->status.pr_sysexit = P->orig_status.pr_sysexit; + if ((P->status.pr_flags & ALL_SETTABLE_FLAGS) != + (flags = (P->orig_status.pr_flags & ALL_SETTABLE_FLAGS))) { + (void) Punsetflags(P, ALL_SETTABLE_FLAGS); + if (flags) + (void) Psetflags(P, flags); + } + } + + cmd[0] = PCSTRACE; + iov[0].iov_base = (caddr_t)&cmd[0]; + iov[0].iov_len = sizeof (long); + iov[1].iov_base = (caddr_t)&P->status.pr_sigtrace; + iov[1].iov_len = sizeof (P->status.pr_sigtrace); + + cmd[1] = PCSFAULT; + iov[2].iov_base = (caddr_t)&cmd[1]; + iov[2].iov_len = sizeof (long); + iov[3].iov_base = (caddr_t)&P->status.pr_flttrace; + iov[3].iov_len = sizeof (P->status.pr_flttrace); + + cmd[2] = PCSENTRY; + iov[4].iov_base = (caddr_t)&cmd[2]; + iov[4].iov_len = sizeof (long); + iov[5].iov_base = (caddr_t)&P->status.pr_sysentry; + iov[5].iov_len = sizeof (P->status.pr_sysentry); + + cmd[3] = PCSEXIT; + iov[6].iov_base = (caddr_t)&cmd[3]; + iov[6].iov_len = sizeof (long); + iov[7].iov_base = (caddr_t)&P->status.pr_sysexit; + iov[7].iov_len = sizeof (P->status.pr_sysexit); + + (void) writev(P->ctlfd, iov, 8); + + P->flags &= ~(SETSIG|SETFAULT|SETENTRY|SETEXIT); +} + +/* + * Release the process. Frees the process control structure. + * flags: + * PRELEASE_CLEAR Clear all tracing flags. + * PRELEASE_RETAIN Retain current tracing flags. + * PRELEASE_HANG Leave the process stopped and abandoned. + * PRELEASE_KILL Terminate the process with SIGKILL. + */ +void +Prelease(struct ps_prochandle *P, int flags) +{ + if (P->state == PS_DEAD) { + dprintf("Prelease: releasing handle %p PS_DEAD of pid %d\n", + (void *)P, (int)P->pid); + Pfree(P); + return; + } + + if (P->state == PS_IDLE) { + file_info_t *fptr = list_next(&P->file_head); + dprintf("Prelease: releasing handle %p PS_IDLE of file %s\n", + (void *)P, fptr->file_pname); + Pfree(P); + return; + } + + dprintf("Prelease: releasing handle %p pid %d\n", + (void *)P, (int)P->pid); + + if (P->ctlfd == -1) { + Pfree(P); + return; + } + + if (P->agentcnt > 0) { + P->agentcnt = 1; + Pdestroy_agent(P); + } + + /* + * Attempt to stop the process. + */ + P->state = PS_RUN; + (void) Pstop(P, 1000); + + if (flags & PRELEASE_KILL) { + if (P->state == PS_STOP) + (void) Psetrun(P, SIGKILL, 0); + (void) kill(P->pid, SIGKILL); + Pfree(P); + return; + } + + /* + * If we lost control, all we can do now is close the files. + * In this case, the last close sets the process running. + */ + if (P->state != PS_STOP && + (P->status.pr_lwp.pr_flags & (PR_ISTOP|PR_DSTOP)) == 0) { + Pfree(P); + return; + } + + /* + * We didn't lose control; we do more. + */ + Psync(P); + + if (flags & PRELEASE_CLEAR) + P->flags |= CREATED; + + if (!(flags & PRELEASE_RETAIN)) + restore_tracing_flags(P); + + if (flags & PRELEASE_HANG) { + /* Leave the process stopped and abandoned */ + (void) Punsetflags(P, PR_RLC|PR_KLC); + Pfree(P); + return; + } + + /* + * Set the process running if we created it or if it was + * not originally stopped or directed to stop via /proc + * or if we were given the PRELEASE_CLEAR flag. + */ + if ((P->flags & CREATED) || + (P->orig_status.pr_lwp.pr_flags & (PR_ISTOP|PR_DSTOP)) == 0) { + (void) Psetflags(P, PR_RLC); + /* + * We do this repeatedly because the process may have + * more than one LWP stopped on an event of interest. + * This makes sure all of them are set running. + */ + do { + if (Psetrun(P, 0, 0) == -1 && errno == EBUSY) + break; /* Agent LWP may be stuck */ + } while (Pstopstatus(P, PCNULL, 0) == 0 && + P->status.pr_lwp.pr_flags & (PR_ISTOP|PR_DSTOP)); + + if (P->status.pr_lwp.pr_flags & (PR_ISTOP|PR_DSTOP)) + dprintf("Prelease: failed to set process running\n"); + } + + Pfree(P); +} + +/* debugging */ +void +prldump(const char *caller, lwpstatus_t *lsp) +{ + char name[32]; + uint32_t bits; + + switch (lsp->pr_why) { + case PR_REQUESTED: + dprintf("%s: REQUESTED\n", caller); + break; + case PR_SIGNALLED: + dprintf("%s: SIGNALLED %s\n", caller, + proc_signame(lsp->pr_what, name, sizeof (name))); + break; + case PR_FAULTED: + dprintf("%s: FAULTED %s\n", caller, + proc_fltname(lsp->pr_what, name, sizeof (name))); + break; + case PR_SYSENTRY: + dprintf("%s: SYSENTRY %s\n", caller, + proc_sysname(lsp->pr_what, name, sizeof (name))); + break; + case PR_SYSEXIT: + dprintf("%s: SYSEXIT %s\n", caller, + proc_sysname(lsp->pr_what, name, sizeof (name))); + break; + case PR_JOBCONTROL: + dprintf("%s: JOBCONTROL %s\n", caller, + proc_signame(lsp->pr_what, name, sizeof (name))); + break; + case PR_SUSPENDED: + dprintf("%s: SUSPENDED\n", caller); + break; + default: + dprintf("%s: Unknown\n", caller); + break; + } + + if (lsp->pr_cursig) + dprintf("%s: p_cursig = %d\n", caller, lsp->pr_cursig); + + bits = *((uint32_t *)&lsp->pr_lwppend); + if (bits) + dprintf("%s: pr_lwppend = 0x%.8X\n", caller, bits); +} + +/* debugging */ +static void +prdump(struct ps_prochandle *P) +{ + uint32_t bits; + + prldump("Pstopstatus", &P->status.pr_lwp); + + bits = *((uint32_t *)&P->status.pr_sigpend); + if (bits) + dprintf("Pstopstatus: pr_sigpend = 0x%.8X\n", bits); +} + +/* + * Wait for the specified process to stop or terminate. + * Or, just get the current status (PCNULL). + * Or, direct it to stop and get the current status (PCDSTOP). + * If the agent LWP exists, do these things to the agent, + * else do these things to the process as a whole. + */ +int +Pstopstatus(struct ps_prochandle *P, + long request, /* PCNULL, PCDSTOP, PCSTOP, PCWSTOP */ + uint_t msec) /* if non-zero, timeout in milliseconds */ +{ + int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd; + long ctl[3]; + ssize_t rc; + int err; + int old_state = P->state; + + switch (P->state) { + case PS_RUN: + break; + case PS_STOP: + if (request != PCNULL && request != PCDSTOP) + return (0); + break; + case PS_LOST: + if (request != PCNULL) { + errno = EAGAIN; + return (-1); + } + break; + case PS_UNDEAD: + case PS_DEAD: + case PS_IDLE: + if (request != PCNULL) { + errno = ENOENT; + return (-1); + } + break; + default: /* corrupted state */ + dprintf("Pstopstatus: corrupted state: %d\n", P->state); + errno = EINVAL; + return (-1); + } + + ctl[0] = PCDSTOP; + ctl[1] = PCTWSTOP; + ctl[2] = (long)msec; + rc = 0; + switch (request) { + case PCSTOP: + rc = write(ctlfd, &ctl[0], 3*sizeof (long)); + break; + case PCWSTOP: + rc = write(ctlfd, &ctl[1], 2*sizeof (long)); + break; + case PCDSTOP: + rc = write(ctlfd, &ctl[0], 1*sizeof (long)); + break; + case PCNULL: + if (P->state == PS_DEAD || P->state == PS_IDLE) + return (0); + break; + default: /* programming error */ + errno = EINVAL; + return (-1); + } + err = (rc < 0)? errno : 0; + Psync(P); + + if (P->agentstatfd < 0) { + if (pread(P->statfd, &P->status, + sizeof (P->status), (off_t)0) < 0) + err = errno; + } else { + if (pread(P->agentstatfd, &P->status.pr_lwp, + sizeof (P->status.pr_lwp), (off_t)0) < 0) + err = errno; + P->status.pr_flags = P->status.pr_lwp.pr_flags; + } + + if (err) { + switch (err) { + case EINTR: /* user typed ctl-C */ + case ERESTART: + dprintf("Pstopstatus: EINTR\n"); + break; + case EAGAIN: /* we lost control of the the process */ + case EOVERFLOW: + dprintf("Pstopstatus: PS_LOST, errno=%d\n", err); + P->state = PS_LOST; + break; + default: /* check for dead process */ + if (_libproc_debug) { + const char *errstr; + + switch (request) { + case PCNULL: + errstr = "Pstopstatus PCNULL"; break; + case PCSTOP: + errstr = "Pstopstatus PCSTOP"; break; + case PCDSTOP: + errstr = "Pstopstatus PCDSTOP"; break; + case PCWSTOP: + errstr = "Pstopstatus PCWSTOP"; break; + default: + errstr = "Pstopstatus PC???"; break; + } + dprintf("%s: %s\n", errstr, strerror(err)); + } + deadcheck(P); + break; + } + if (err != EINTR && err != ERESTART) { + errno = err; + return (-1); + } + } + + if (!(P->status.pr_flags & PR_STOPPED)) { + P->state = PS_RUN; + if (request == PCNULL || request == PCDSTOP || msec != 0) + return (0); + dprintf("Pstopstatus: process is not stopped\n"); + errno = EPROTO; + return (-1); + } + + P->state = PS_STOP; + + if (_libproc_debug) /* debugging */ + prdump(P); + + /* + * If the process was already stopped coming into Pstopstatus(), + * then don't use its PC to set P->sysaddr since it may have been + * changed since the time the process originally stopped. + */ + if (old_state == PS_STOP) + return (0); + + switch (P->status.pr_lwp.pr_why) { + case PR_SYSENTRY: + case PR_SYSEXIT: + if (Pissyscall_prev(P, P->status.pr_lwp.pr_reg[R_PC], + &P->sysaddr) == 0) + P->sysaddr = P->status.pr_lwp.pr_reg[R_PC]; + break; + case PR_REQUESTED: + case PR_SIGNALLED: + case PR_FAULTED: + case PR_JOBCONTROL: + case PR_SUSPENDED: + break; + default: + errno = EPROTO; + return (-1); + } + + return (0); +} + +/* + * Wait for the process to stop for any reason. + */ +int +Pwait(struct ps_prochandle *P, uint_t msec) +{ + return (Pstopstatus(P, PCWSTOP, msec)); +} + +/* + * Direct the process to stop; wait for it to stop. + */ +int +Pstop(struct ps_prochandle *P, uint_t msec) +{ + return (Pstopstatus(P, PCSTOP, msec)); +} + +/* + * Direct the process to stop; don't wait. + */ +int +Pdstop(struct ps_prochandle *P) +{ + return (Pstopstatus(P, PCDSTOP, 0)); +} + +static void +deadcheck(struct ps_prochandle *P) +{ + int fd; + void *buf; + size_t size; + + if (P->statfd < 0) + P->state = PS_UNDEAD; + else { + if (P->agentstatfd < 0) { + fd = P->statfd; + buf = &P->status; + size = sizeof (P->status); + } else { + fd = P->agentstatfd; + buf = &P->status.pr_lwp; + size = sizeof (P->status.pr_lwp); + } + while (pread(fd, buf, size, (off_t)0) != size) { + switch (errno) { + default: + P->state = PS_UNDEAD; + break; + case EINTR: + case ERESTART: + continue; + case EAGAIN: + P->state = PS_LOST; + break; + } + break; + } + P->status.pr_flags = P->status.pr_lwp.pr_flags; + } +} + +/* + * Get the value of one register from stopped process. + */ +int +Pgetareg(struct ps_prochandle *P, int regno, prgreg_t *preg) +{ + if (regno < 0 || regno >= NPRGREG) { + errno = EINVAL; + return (-1); + } + + if (P->state == PS_IDLE) { + errno = ENODATA; + return (-1); + } + + if (P->state != PS_STOP && P->state != PS_DEAD) { + errno = EBUSY; + return (-1); + } + + *preg = P->status.pr_lwp.pr_reg[regno]; + return (0); +} + +/* + * Put value of one register into stopped process. + */ +int +Pputareg(struct ps_prochandle *P, int regno, prgreg_t reg) +{ + if (regno < 0 || regno >= NPRGREG) { + errno = EINVAL; + return (-1); + } + + if (P->state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + P->status.pr_lwp.pr_reg[regno] = reg; + P->flags |= SETREGS; /* set registers before continuing */ + return (0); +} + +int +Psetrun(struct ps_prochandle *P, + int sig, /* signal to pass to process */ + int flags) /* PRSTEP|PRSABORT|PRSTOP|PRCSIG|PRCFAULT */ +{ + int ctlfd = (P->agentctlfd >= 0) ? P->agentctlfd : P->ctlfd; + int sbits = (PR_DSTOP | PR_ISTOP | PR_ASLEEP); + + long ctl[1 + /* PCCFAULT */ + 1 + sizeof (siginfo_t)/sizeof (long) + /* PCSSIG/PCCSIG */ + 2 ]; /* PCRUN */ + + long *ctlp = ctl; + size_t size; + + if (P->state != PS_STOP && (P->status.pr_lwp.pr_flags & sbits) == 0) { + errno = EBUSY; + return (-1); + } + + Psync(P); /* flush tracing flags and registers */ + + if (flags & PRCFAULT) { /* clear current fault */ + *ctlp++ = PCCFAULT; + flags &= ~PRCFAULT; + } + + if (flags & PRCSIG) { /* clear current signal */ + *ctlp++ = PCCSIG; + flags &= ~PRCSIG; + } else if (sig && sig != P->status.pr_lwp.pr_cursig) { + /* make current signal */ + siginfo_t *infop; + + *ctlp++ = PCSSIG; + infop = (siginfo_t *)ctlp; + (void) memset(infop, 0, sizeof (*infop)); + infop->si_signo = sig; + ctlp += sizeof (siginfo_t) / sizeof (long); + } + + *ctlp++ = PCRUN; + *ctlp++ = flags; + size = (char *)ctlp - (char *)ctl; + + P->info_valid = 0; /* will need to update map and file info */ + + /* + * If we've cached ucontext-list information while we were stopped, + * free it now. + */ + if (P->ucaddrs != NULL) { + free(P->ucaddrs); + P->ucaddrs = NULL; + P->ucnelems = 0; + } + + if (write(ctlfd, ctl, size) != size) { + /* If it is dead or lost, return the real status, not PS_RUN */ + if (errno == ENOENT || errno == EAGAIN) { + (void) Pstopstatus(P, PCNULL, 0); + return (0); + } + /* If it is not in a jobcontrol stop, issue an error message */ + if (errno != EBUSY || + P->status.pr_lwp.pr_why != PR_JOBCONTROL) { + dprintf("Psetrun: %s\n", strerror(errno)); + return (-1); + } + /* Otherwise pretend that the job-stopped process is running */ + } + + P->state = PS_RUN; + return (0); +} + +ssize_t +Pread(struct ps_prochandle *P, + void *buf, /* caller's buffer */ + size_t nbyte, /* number of bytes to read */ + uintptr_t address) /* address in process */ +{ + return (P->ops->p_pread(P, buf, nbyte, address)); +} + +ssize_t +Pread_string(struct ps_prochandle *P, + char *buf, /* caller's buffer */ + size_t size, /* upper limit on bytes to read */ + uintptr_t addr) /* address in process */ +{ + enum { STRSZ = 40 }; + char string[STRSZ + 1]; + ssize_t leng = 0; + int nbyte; + + if (size < 2) { + errno = EINVAL; + return (-1); + } + + size--; /* ensure trailing null fits in buffer */ + + *buf = '\0'; + string[STRSZ] = '\0'; + + for (nbyte = STRSZ; nbyte == STRSZ && leng < size; addr += STRSZ) { + if ((nbyte = P->ops->p_pread(P, string, STRSZ, addr)) <= 0) { + buf[leng] = '\0'; + return (leng ? leng : -1); + } + if ((nbyte = strlen(string)) > 0) { + if (leng + nbyte > size) + nbyte = size - leng; + (void) strncpy(buf + leng, string, nbyte); + leng += nbyte; + } + } + buf[leng] = '\0'; + return (leng); +} + +ssize_t +Pwrite(struct ps_prochandle *P, + const void *buf, /* caller's buffer */ + size_t nbyte, /* number of bytes to write */ + uintptr_t address) /* address in process */ +{ + return (P->ops->p_pwrite(P, buf, nbyte, address)); +} + +int +Pclearsig(struct ps_prochandle *P) +{ + int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd; + long ctl = PCCSIG; + + if (write(ctlfd, &ctl, sizeof (ctl)) != sizeof (ctl)) + return (-1); + P->status.pr_lwp.pr_cursig = 0; + return (0); +} + +int +Pclearfault(struct ps_prochandle *P) +{ + int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd; + long ctl = PCCFAULT; + + if (write(ctlfd, &ctl, sizeof (ctl)) != sizeof (ctl)) + return (-1); + return (0); +} + +/* + * Set a breakpoint trap, return original instruction. + */ +int +Psetbkpt(struct ps_prochandle *P, uintptr_t address, ulong_t *saved) +{ + long ctl[1 + sizeof (priovec_t) / sizeof (long) + /* PCREAD */ + 1 + sizeof (priovec_t) / sizeof (long)]; /* PCWRITE */ + long *ctlp = ctl; + size_t size; + priovec_t *iovp; + instr_t bpt = BPT; + instr_t old; + + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + /* fetch the old instruction */ + *ctlp++ = PCREAD; + iovp = (priovec_t *)ctlp; + iovp->pio_base = &old; + iovp->pio_len = sizeof (old); + iovp->pio_offset = address; + ctlp += sizeof (priovec_t) / sizeof (long); + + /* write the BPT instruction */ + *ctlp++ = PCWRITE; + iovp = (priovec_t *)ctlp; + iovp->pio_base = &bpt; + iovp->pio_len = sizeof (bpt); + iovp->pio_offset = address; + ctlp += sizeof (priovec_t) / sizeof (long); + + size = (char *)ctlp - (char *)ctl; + if (write(P->ctlfd, ctl, size) != size) + return (-1); + + /* + * Fail if there was already a breakpoint there from another debugger + * or DTrace's user-level tracing on x86. + */ + if (old == BPT) + return (EBUSY); + + *saved = (ulong_t)old; + return (0); +} + +/* + * Restore original instruction where a breakpoint was set. + */ +int +Pdelbkpt(struct ps_prochandle *P, uintptr_t address, ulong_t saved) +{ + instr_t old = (instr_t)saved; + instr_t cur; + + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + /* + * If the breakpoint instruction we had placed has been overwritten + * with a new instruction, then don't try to replace it with the + * old instruction. Doing do can cause problems with self-modifying + * code -- PLTs for example. If the Pread() fails, we assume that we + * should proceed though most likely the Pwrite() will also fail. + */ + if (Pread(P, &cur, sizeof (cur), address) == sizeof (cur) && + cur != BPT) + return (0); + + if (Pwrite(P, &old, sizeof (old), address) != sizeof (old)) + return (-1); + + return (0); +} + +/* + * Common code for Pxecbkpt() and Lxecbkpt(). + * Develop the array of requests that will do the job, then + * write them to the specified control file descriptor. + * Return the non-zero errno if the write fails. + */ +static int +execute_bkpt( + int ctlfd, /* process or LWP control file descriptor */ + const fltset_t *faultset, /* current set of traced faults */ + const sigset_t *sigmask, /* current signal mask */ + uintptr_t address, /* address of breakpint */ + ulong_t saved) /* the saved instruction */ +{ + long ctl[ + 1 + sizeof (sigset_t) / sizeof (long) + /* PCSHOLD */ + 1 + sizeof (fltset_t) / sizeof (long) + /* PCSFAULT */ + 1 + sizeof (priovec_t) / sizeof (long) + /* PCWRITE */ + 2 + /* PCRUN */ + 1 + /* PCWSTOP */ + 1 + /* PCCFAULT */ + 1 + sizeof (priovec_t) / sizeof (long) + /* PCWRITE */ + 1 + sizeof (fltset_t) / sizeof (long) + /* PCSFAULT */ + 1 + sizeof (sigset_t) / sizeof (long)]; /* PCSHOLD */ + long *ctlp = ctl; + sigset_t unblock; + size_t size; + ssize_t ssize; + priovec_t *iovp; + sigset_t *holdp; + fltset_t *faultp; + instr_t old = (instr_t)saved; + instr_t bpt = BPT; + int error = 0; + + /* block our signals for the duration */ + (void) sigprocmask(SIG_BLOCK, &blockable_sigs, &unblock); + + /* hold posted signals */ + *ctlp++ = PCSHOLD; + holdp = (sigset_t *)ctlp; + prfillset(holdp); + prdelset(holdp, SIGKILL); + prdelset(holdp, SIGSTOP); + ctlp += sizeof (sigset_t) / sizeof (long); + + /* force tracing of FLTTRACE */ + if (!(prismember(faultset, FLTTRACE))) { + *ctlp++ = PCSFAULT; + faultp = (fltset_t *)ctlp; + *faultp = *faultset; + praddset(faultp, FLTTRACE); + ctlp += sizeof (fltset_t) / sizeof (long); + } + + /* restore the old instruction */ + *ctlp++ = PCWRITE; + iovp = (priovec_t *)ctlp; + iovp->pio_base = &old; + iovp->pio_len = sizeof (old); + iovp->pio_offset = address; + ctlp += sizeof (priovec_t) / sizeof (long); + + /* clear current signal and fault; set running w/ single-step */ + *ctlp++ = PCRUN; + *ctlp++ = PRCSIG | PRCFAULT | PRSTEP; + + /* wait for stop, cancel the fault */ + *ctlp++ = PCWSTOP; + *ctlp++ = PCCFAULT; + + /* restore the breakpoint trap */ + *ctlp++ = PCWRITE; + iovp = (priovec_t *)ctlp; + iovp->pio_base = &bpt; + iovp->pio_len = sizeof (bpt); + iovp->pio_offset = address; + ctlp += sizeof (priovec_t) / sizeof (long); + + /* restore fault tracing set */ + if (!(prismember(faultset, FLTTRACE))) { + *ctlp++ = PCSFAULT; + *(fltset_t *)ctlp = *faultset; + ctlp += sizeof (fltset_t) / sizeof (long); + } + + /* restore the hold mask */ + *ctlp++ = PCSHOLD; + *(sigset_t *)ctlp = *sigmask; + ctlp += sizeof (sigset_t) / sizeof (long); + + size = (char *)ctlp - (char *)ctl; + if ((ssize = write(ctlfd, ctl, size)) != size) + error = (ssize == -1)? errno : EINTR; + (void) sigprocmask(SIG_SETMASK, &unblock, NULL); + return (error); +} + +/* + * Step over a breakpoint, i.e., execute the instruction that + * really belongs at the breakpoint location (the current %pc) + * and leave the process stopped at the next instruction. + */ +int +Pxecbkpt(struct ps_prochandle *P, ulong_t saved) +{ + int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd; + int rv, error; + + if (P->state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + Psync(P); + + error = execute_bkpt(ctlfd, + &P->status.pr_flttrace, &P->status.pr_lwp.pr_lwphold, + P->status.pr_lwp.pr_reg[R_PC], saved); + rv = Pstopstatus(P, PCNULL, 0); + + if (error != 0) { + if (P->status.pr_lwp.pr_why == PR_JOBCONTROL && + error == EBUSY) { /* jobcontrol stop -- back off */ + P->state = PS_RUN; + return (0); + } + if (error == ENOENT) + return (0); + errno = error; + return (-1); + } + + return (rv); +} + +/* + * Install the watchpoint described by wp. + */ +int +Psetwapt(struct ps_prochandle *P, const prwatch_t *wp) +{ + long ctl[1 + sizeof (prwatch_t) / sizeof (long)]; + prwatch_t *cwp = (prwatch_t *)&ctl[1]; + + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + ctl[0] = PCWATCH; + cwp->pr_vaddr = wp->pr_vaddr; + cwp->pr_size = wp->pr_size; + cwp->pr_wflags = wp->pr_wflags; + + if (write(P->ctlfd, ctl, sizeof (ctl)) != sizeof (ctl)) + return (-1); + + return (0); +} + +/* + * Remove the watchpoint described by wp. + */ +int +Pdelwapt(struct ps_prochandle *P, const prwatch_t *wp) +{ + long ctl[1 + sizeof (prwatch_t) / sizeof (long)]; + prwatch_t *cwp = (prwatch_t *)&ctl[1]; + + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + ctl[0] = PCWATCH; + cwp->pr_vaddr = wp->pr_vaddr; + cwp->pr_size = wp->pr_size; + cwp->pr_wflags = 0; + + if (write(P->ctlfd, ctl, sizeof (ctl)) != sizeof (ctl)) + return (-1); + + return (0); +} + +/* + * Common code for Pxecwapt() and Lxecwapt(). Develop the array of requests + * that will do the job, then write them to the specified control file + * descriptor. Return the non-zero errno if the write fails. + */ +static int +execute_wapt( + int ctlfd, /* process or LWP control file descriptor */ + const fltset_t *faultset, /* current set of traced faults */ + const sigset_t *sigmask, /* current signal mask */ + const prwatch_t *wp) /* watchpoint descriptor */ +{ + long ctl[ + 1 + sizeof (sigset_t) / sizeof (long) + /* PCSHOLD */ + 1 + sizeof (fltset_t) / sizeof (long) + /* PCSFAULT */ + 1 + sizeof (prwatch_t) / sizeof (long) + /* PCWATCH */ + 2 + /* PCRUN */ + 1 + /* PCWSTOP */ + 1 + /* PCCFAULT */ + 1 + sizeof (prwatch_t) / sizeof (long) + /* PCWATCH */ + 1 + sizeof (fltset_t) / sizeof (long) + /* PCSFAULT */ + 1 + sizeof (sigset_t) / sizeof (long)]; /* PCSHOLD */ + + long *ctlp = ctl; + int error = 0; + + sigset_t unblock; + sigset_t *holdp; + fltset_t *faultp; + prwatch_t *prw; + ssize_t ssize; + size_t size; + + (void) sigprocmask(SIG_BLOCK, &blockable_sigs, &unblock); + + /* + * Hold all posted signals in the victim process prior to stepping. + */ + *ctlp++ = PCSHOLD; + holdp = (sigset_t *)ctlp; + prfillset(holdp); + prdelset(holdp, SIGKILL); + prdelset(holdp, SIGSTOP); + ctlp += sizeof (sigset_t) / sizeof (long); + + /* + * Force tracing of FLTTRACE since we need to single step. + */ + if (!(prismember(faultset, FLTTRACE))) { + *ctlp++ = PCSFAULT; + faultp = (fltset_t *)ctlp; + *faultp = *faultset; + praddset(faultp, FLTTRACE); + ctlp += sizeof (fltset_t) / sizeof (long); + } + + /* + * Clear only the current watchpoint by setting pr_wflags to zero. + */ + *ctlp++ = PCWATCH; + prw = (prwatch_t *)ctlp; + prw->pr_vaddr = wp->pr_vaddr; + prw->pr_size = wp->pr_size; + prw->pr_wflags = 0; + ctlp += sizeof (prwatch_t) / sizeof (long); + + /* + * Clear the current signal and fault; set running with single-step. + * Then wait for the victim to stop and cancel the FLTTRACE. + */ + *ctlp++ = PCRUN; + *ctlp++ = PRCSIG | PRCFAULT | PRSTEP; + *ctlp++ = PCWSTOP; + *ctlp++ = PCCFAULT; + + /* + * Restore the current watchpoint. + */ + *ctlp++ = PCWATCH; + (void) memcpy(ctlp, wp, sizeof (prwatch_t)); + ctlp += sizeof (prwatch_t) / sizeof (long); + + /* + * Restore fault tracing set if we modified it. + */ + if (!(prismember(faultset, FLTTRACE))) { + *ctlp++ = PCSFAULT; + *(fltset_t *)ctlp = *faultset; + ctlp += sizeof (fltset_t) / sizeof (long); + } + + /* + * Restore the hold mask to the current hold mask (i.e. the one + * before we executed any of the previous operations). + */ + *ctlp++ = PCSHOLD; + *(sigset_t *)ctlp = *sigmask; + ctlp += sizeof (sigset_t) / sizeof (long); + + size = (char *)ctlp - (char *)ctl; + if ((ssize = write(ctlfd, ctl, size)) != size) + error = (ssize == -1)? errno : EINTR; + (void) sigprocmask(SIG_SETMASK, &unblock, NULL); + return (error); +} + +/* + * Step over a watchpoint, i.e., execute the instruction that was stopped by + * the watchpoint, and then leave the LWP stopped at the next instruction. + */ +int +Pxecwapt(struct ps_prochandle *P, const prwatch_t *wp) +{ + int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd; + int rv, error; + + if (P->state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + Psync(P); + error = execute_wapt(ctlfd, + &P->status.pr_flttrace, &P->status.pr_lwp.pr_lwphold, wp); + rv = Pstopstatus(P, PCNULL, 0); + + if (error != 0) { + if (P->status.pr_lwp.pr_why == PR_JOBCONTROL && + error == EBUSY) { /* jobcontrol stop -- back off */ + P->state = PS_RUN; + return (0); + } + if (error == ENOENT) + return (0); + errno = error; + return (-1); + } + + return (rv); +} + +int +Psetflags(struct ps_prochandle *P, long flags) +{ + int rc; + long ctl[2]; + + ctl[0] = PCSET; + ctl[1] = flags; + + if (write(P->ctlfd, ctl, 2*sizeof (long)) != 2*sizeof (long)) { + rc = -1; + } else { + P->status.pr_flags |= flags; + P->status.pr_lwp.pr_flags |= flags; + rc = 0; + } + + return (rc); +} + +int +Punsetflags(struct ps_prochandle *P, long flags) +{ + int rc; + long ctl[2]; + + ctl[0] = PCUNSET; + ctl[1] = flags; + + if (write(P->ctlfd, ctl, 2*sizeof (long)) != 2*sizeof (long)) { + rc = -1; + } else { + P->status.pr_flags &= ~flags; + P->status.pr_lwp.pr_flags &= ~flags; + rc = 0; + } + + return (rc); +} + +/* + * Common function to allow clients to manipulate the action to be taken + * on receipt of a signal, receipt of machine fault, entry to a system call, + * or exit from a system call. We make use of our private prset_* functions + * in order to make this code be common. The 'which' parameter identifies + * the code for the event of interest (0 means change the entire set), and + * the 'stop' parameter is a boolean indicating whether the process should + * stop when the event of interest occurs. The previous value is returned + * to the caller; -1 is returned if an error occurred. + */ +static int +Psetaction(struct ps_prochandle *P, void *sp, size_t size, + uint_t flag, int max, int which, int stop) +{ + int oldval; + + if (which < 0 || which > max) { + errno = EINVAL; + return (-1); + } + + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + oldval = prset_ismember(sp, size, which) ? TRUE : FALSE; + + if (stop) { + if (which == 0) { + prset_fill(sp, size); + P->flags |= flag; + } else if (!oldval) { + prset_add(sp, size, which); + P->flags |= flag; + } + } else { + if (which == 0) { + prset_empty(sp, size); + P->flags |= flag; + } else if (oldval) { + prset_del(sp, size, which); + P->flags |= flag; + } + } + + if (P->state == PS_RUN) + Psync(P); + + return (oldval); +} + +/* + * Set action on specified signal. + */ +int +Psignal(struct ps_prochandle *P, int which, int stop) +{ + int oldval; + + if (which == SIGKILL && stop != 0) { + errno = EINVAL; + return (-1); + } + + oldval = Psetaction(P, &P->status.pr_sigtrace, sizeof (sigset_t), + SETSIG, PRMAXSIG, which, stop); + + if (oldval != -1 && which == 0 && stop != 0) + prdelset(&P->status.pr_sigtrace, SIGKILL); + + return (oldval); +} + +/* + * Set all signal tracing flags. + */ +void +Psetsignal(struct ps_prochandle *P, const sigset_t *set) +{ + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) + return; + + P->status.pr_sigtrace = *set; + P->flags |= SETSIG; + + if (P->state == PS_RUN) + Psync(P); +} + +/* + * Set action on specified fault. + */ +int +Pfault(struct ps_prochandle *P, int which, int stop) +{ + return (Psetaction(P, &P->status.pr_flttrace, sizeof (fltset_t), + SETFAULT, PRMAXFAULT, which, stop)); +} + +/* + * Set all machine fault tracing flags. + */ +void +Psetfault(struct ps_prochandle *P, const fltset_t *set) +{ + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) + return; + + P->status.pr_flttrace = *set; + P->flags |= SETFAULT; + + if (P->state == PS_RUN) + Psync(P); +} + +/* + * Set action on specified system call entry. + */ +int +Psysentry(struct ps_prochandle *P, int which, int stop) +{ + return (Psetaction(P, &P->status.pr_sysentry, sizeof (sysset_t), + SETENTRY, PRMAXSYS, which, stop)); +} + +/* + * Set all system call entry tracing flags. + */ +void +Psetsysentry(struct ps_prochandle *P, const sysset_t *set) +{ + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) + return; + + P->status.pr_sysentry = *set; + P->flags |= SETENTRY; + + if (P->state == PS_RUN) + Psync(P); +} + +/* + * Set action on specified system call exit. + */ +int +Psysexit(struct ps_prochandle *P, int which, int stop) +{ + return (Psetaction(P, &P->status.pr_sysexit, sizeof (sysset_t), + SETEXIT, PRMAXSYS, which, stop)); +} + +/* + * Set all system call exit tracing flags. + */ +void +Psetsysexit(struct ps_prochandle *P, const sysset_t *set) +{ + if (P->state == PS_DEAD || P->state == PS_UNDEAD || + P->state == PS_IDLE) + return; + + P->status.pr_sysexit = *set; + P->flags |= SETEXIT; + + if (P->state == PS_RUN) + Psync(P); +} + +/* + * Utility function to read the contents of a file that contains a + * prheader_t at the start (/proc/pid/lstatus or /proc/pid/lpsinfo). + * Returns a malloc()d buffer or NULL on failure. + */ +static prheader_t * +read_lfile(struct ps_prochandle *P, const char *lname) +{ + prheader_t *Lhp; + char lpath[64]; + struct stat64 statb; + int fd; + size_t size; + ssize_t rval; + + (void) snprintf(lpath, sizeof (lpath), "/proc/%d/%s", + (int)P->status.pr_pid, lname); + if ((fd = open(lpath, O_RDONLY)) < 0 || fstat64(fd, &statb) != 0) { + if (fd >= 0) + (void) close(fd); + return (NULL); + } + + /* + * 'size' is just the initial guess at the buffer size. + * It will have to grow if the number of lwps increases + * while we are looking at the process. + * 'size' must be larger than the actual file size. + */ + size = statb.st_size + 32; + + for (;;) { + if ((Lhp = malloc(size)) == NULL) + break; + if ((rval = pread(fd, Lhp, size, 0)) < 0 || + rval <= sizeof (prheader_t)) { + free(Lhp); + Lhp = NULL; + break; + } + if (rval < size) + break; + /* need a bigger buffer */ + free(Lhp); + size *= 2; + } + + (void) close(fd); + return (Lhp); +} + +/* + * LWP iteration interface. + */ +int +Plwp_iter(struct ps_prochandle *P, proc_lwp_f *func, void *cd) +{ + prheader_t *Lhp; + lwpstatus_t *Lsp; + long nlwp; + int rv; + + switch (P->state) { + case PS_RUN: + (void) Pstopstatus(P, PCNULL, 0); + break; + + case PS_STOP: + Psync(P); + break; + + case PS_IDLE: + errno = ENODATA; + return (-1); + } + + /* + * For either live processes or cores, the single LWP case is easy: + * the pstatus_t contains the lwpstatus_t for the only LWP. + */ + if (P->status.pr_nlwp <= 1) + return (func(cd, &P->status.pr_lwp)); + + /* + * For the core file multi-LWP case, we just iterate through the + * list of LWP structs we read in from the core file. + */ + if (P->state == PS_DEAD) { + lwp_info_t *lwp = list_prev(&P->core->core_lwp_head); + uint_t i; + + for (i = 0; i < P->core->core_nlwp; i++, lwp = list_prev(lwp)) { + if (lwp->lwp_psinfo.pr_sname != 'Z' && + (rv = func(cd, &lwp->lwp_status)) != 0) + break; + } + + return (rv); + } + + /* + * For the live process multi-LWP case, we have to work a little + * harder: the /proc/pid/lstatus file has the array of LWP structs. + */ + if ((Lhp = read_lfile(P, "lstatus")) == NULL) + return (-1); + + for (nlwp = Lhp->pr_nent, Lsp = (lwpstatus_t *)(uintptr_t)(Lhp + 1); + nlwp > 0; + nlwp--, Lsp = (lwpstatus_t *)((uintptr_t)Lsp + Lhp->pr_entsize)) { + if ((rv = func(cd, Lsp)) != 0) + break; + } + + free(Lhp); + return (rv); +} + +/* + * Extended LWP iteration interface. + * Iterate over all LWPs, active and zombie. + */ +int +Plwp_iter_all(struct ps_prochandle *P, proc_lwp_all_f *func, void *cd) +{ + prheader_t *Lhp = NULL; + lwpstatus_t *Lsp; + lwpstatus_t *sp; + prheader_t *Lphp = NULL; + lwpsinfo_t *Lpsp; + long nstat; + long ninfo; + int rv; + +retry: + if (Lhp != NULL) + free(Lhp); + if (Lphp != NULL) + free(Lphp); + if (P->state == PS_RUN) + (void) Pstopstatus(P, PCNULL, 0); + (void) Ppsinfo(P); + + if (P->state == PS_STOP) + Psync(P); + + /* + * For either live processes or cores, the single LWP case is easy: + * the pstatus_t contains the lwpstatus_t for the only LWP and + * the psinfo_t contains the lwpsinfo_t for the only LWP. + */ + if (P->status.pr_nlwp + P->status.pr_nzomb <= 1) + return (func(cd, &P->status.pr_lwp, &P->psinfo.pr_lwp)); + + /* + * For the core file multi-LWP case, we just iterate through the + * list of LWP structs we read in from the core file. + */ + if (P->state == PS_DEAD) { + lwp_info_t *lwp = list_prev(&P->core->core_lwp_head); + uint_t i; + + for (i = 0; i < P->core->core_nlwp; i++, lwp = list_prev(lwp)) { + sp = (lwp->lwp_psinfo.pr_sname == 'Z')? NULL : + &lwp->lwp_status; + if ((rv = func(cd, sp, &lwp->lwp_psinfo)) != 0) + break; + } + + return (rv); + } + + /* + * For the live process multi-LWP case, we have to work a little + * harder: the /proc/pid/lstatus file has the array of lwpstatus_t's + * and the /proc/pid/lpsinfo file has the array of lwpsinfo_t's. + */ + if ((Lhp = read_lfile(P, "lstatus")) == NULL) + return (-1); + if ((Lphp = read_lfile(P, "lpsinfo")) == NULL) { + free(Lhp); + return (-1); + } + + /* + * If we are looking at a running process, or one we do not control, + * the active and zombie lwps in the process may have changed since + * we read the process status structure. If so, just start over. + */ + if (Lhp->pr_nent != P->status.pr_nlwp || + Lphp->pr_nent != P->status.pr_nlwp + P->status.pr_nzomb) + goto retry; + + /* + * To be perfectly safe, prescan the two arrays, checking consistency. + * We rely on /proc giving us lwpstatus_t's and lwpsinfo_t's in the + * same order (the lwp directory order) in their respective files. + * We also rely on there being (possibly) more lwpsinfo_t's than + * lwpstatus_t's (the extra lwpsinfo_t's are for zombie lwps). + */ + Lsp = (lwpstatus_t *)(uintptr_t)(Lhp + 1); + Lpsp = (lwpsinfo_t *)(uintptr_t)(Lphp + 1); + nstat = Lhp->pr_nent; + for (ninfo = Lphp->pr_nent; ninfo != 0; ninfo--) { + if (Lpsp->pr_sname != 'Z') { + /* + * Not a zombie lwp; check for matching lwpids. + */ + if (nstat == 0 || Lsp->pr_lwpid != Lpsp->pr_lwpid) + goto retry; + Lsp = (lwpstatus_t *)((uintptr_t)Lsp + Lhp->pr_entsize); + nstat--; + } + Lpsp = (lwpsinfo_t *)((uintptr_t)Lpsp + Lphp->pr_entsize); + } + if (nstat != 0) + goto retry; + + /* + * Rescan, this time for real. + */ + Lsp = (lwpstatus_t *)(uintptr_t)(Lhp + 1); + Lpsp = (lwpsinfo_t *)(uintptr_t)(Lphp + 1); + for (ninfo = Lphp->pr_nent; ninfo != 0; ninfo--) { + if (Lpsp->pr_sname != 'Z') { + sp = Lsp; + Lsp = (lwpstatus_t *)((uintptr_t)Lsp + Lhp->pr_entsize); + } else { + sp = NULL; + } + if ((rv = func(cd, sp, Lpsp)) != 0) + break; + Lpsp = (lwpsinfo_t *)((uintptr_t)Lpsp + Lphp->pr_entsize); + } + + free(Lhp); + free(Lphp); + return (rv); +} + +core_content_t +Pcontent(struct ps_prochandle *P) +{ + if (P->state == PS_DEAD) + return (P->core->core_content); + if (P->state == PS_IDLE) + return (CC_CONTENT_TEXT | CC_CONTENT_DATA | CC_CONTENT_CTF); + + return (CC_CONTENT_ALL); +} + +/* + * ================================================================= + * The remainder of the functions in this file are for the + * control of individual LWPs in the controlled process. + * ================================================================= + */ + +/* + * Find an entry in the process hash table for the specified lwpid. + * The entry will either point to an existing struct ps_lwphandle + * 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) +{ + struct ps_lwphandle **Lp; + struct ps_lwphandle *L; + + for (Lp = &P->hashtab[lwpid % (HASHSIZE - 1)]; + (L = *Lp) != NULL; Lp = &L->lwp_hash) + if (L->lwp_id == lwpid) + break; + return (Lp); +} + +/* + * Grab an LWP contained within the controlled process. + * Return an opaque pointer to its LWP control structure. + * perr: pointer to error return code. + */ +struct ps_lwphandle * +Lgrab(struct ps_prochandle *P, lwpid_t lwpid, int *perr) +{ + struct ps_lwphandle **Lp; + struct ps_lwphandle *L; + int fd; + char procname[100]; + char *fname; + int rc = 0; + + (void) mutex_lock(&P->proc_lock); + + if (P->state == PS_UNDEAD || P->state == PS_IDLE) + rc = G_NOPROC; + else if (P->hashtab == NULL && + (P->hashtab = calloc(HASHSIZE, sizeof (struct ps_lwphandle *))) + == NULL) + rc = G_STRANGE; + else if (*(Lp = Lfind(P, lwpid)) != NULL) + rc = G_BUSY; + else if ((L = malloc(sizeof (struct ps_lwphandle))) == NULL) + rc = G_STRANGE; + if (rc) { + *perr = rc; + (void) mutex_unlock(&P->proc_lock); + return (NULL); + } + + (void) memset(L, 0, sizeof (*L)); + L->lwp_ctlfd = -1; + L->lwp_statfd = -1; + L->lwp_proc = P; + L->lwp_id = lwpid; + *Lp = L; /* insert into the hash table */ + + if (P->state == PS_DEAD) { /* core file */ + if (getlwpstatus(P, lwpid, &L->lwp_status) == -1) { + rc = G_NOPROC; + goto err; + } + L->lwp_state = PS_DEAD; + *perr = 0; + (void) mutex_unlock(&P->proc_lock); + return (L); + } + + /* + * Open the /proc/<pid>/lwp/<lwpid> files + */ + (void) sprintf(procname, "/proc/%d/lwp/%d/", (int)P->pid, (int)lwpid); + fname = procname + strlen(procname); + (void) set_minfd(); + + (void) strcpy(fname, "lwpstatus"); + if ((fd = open(procname, O_RDONLY)) < 0 || + (fd = dupfd(fd, 0)) < 0) { + switch (errno) { + case ENOENT: + rc = G_NOPROC; + break; + default: + dprintf("Lgrab: failed to open %s: %s\n", + procname, strerror(errno)); + rc = G_STRANGE; + break; + } + goto err; + } + L->lwp_statfd = fd; + + if (pread(fd, &L->lwp_status, sizeof (L->lwp_status), (off_t)0) < 0) { + switch (errno) { + case ENOENT: + rc = G_NOPROC; + break; + default: + dprintf("Lgrab: failed to read %s: %s\n", + procname, strerror(errno)); + rc = G_STRANGE; + break; + } + goto err; + } + + (void) strcpy(fname, "lwpctl"); + if ((fd = open(procname, O_WRONLY)) < 0 || + (fd = dupfd(fd, 0)) < 0) { + switch (errno) { + case ENOENT: + rc = G_NOPROC; + break; + default: + dprintf("Lgrab: failed to open %s: %s\n", + procname, strerror(errno)); + rc = G_STRANGE; + break; + } + goto err; + } + L->lwp_ctlfd = fd; + + L->lwp_state = + ((L->lwp_status.pr_flags & (PR_STOPPED|PR_ISTOP)) + == (PR_STOPPED|PR_ISTOP))? + PS_STOP : PS_RUN; + + *perr = 0; + (void) mutex_unlock(&P->proc_lock); + return (L); + +err: + Lfree_internal(P, L); + *perr = rc; + (void) mutex_unlock(&P->proc_lock); + return (NULL); +} + +/* + * Return a printable string corresponding to an Lgrab() error return. + */ +const char * +Lgrab_error(int error) +{ + const char *str; + + switch (error) { + case G_NOPROC: + str = "no such LWP"; + break; + case G_BUSY: + str = "LWP already grabbed"; + break; + case G_STRANGE: + str = "unanticipated system error"; + break; + default: + str = "unknown error"; + break; + } + + return (str); +} + +/* + * Free an LWP control structure. + */ +void +Lfree(struct ps_lwphandle *L) +{ + struct ps_prochandle *P = L->lwp_proc; + + (void) mutex_lock(&P->proc_lock); + Lfree_internal(P, L); + (void) mutex_unlock(&P->proc_lock); +} + +static void +Lfree_internal(struct ps_prochandle *P, struct ps_lwphandle *L) +{ + *Lfind(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) + (void) close(L->lwp_statfd); + + /* clear out the structure as a precaution against reuse */ + (void) memset(L, 0, sizeof (*L)); + L->lwp_ctlfd = -1; + L->lwp_statfd = -1; + + free(L); +} + +/* + * Return the state of the process, one of the PS_* values. + */ +int +Lstate(struct ps_lwphandle *L) +{ + return (L->lwp_state); +} + +/* + * Return the open control file descriptor for the LWP. + * Clients must not close this file descriptor, nor use it + * after the LWP is freed. + */ +int +Lctlfd(struct ps_lwphandle *L) +{ + return (L->lwp_ctlfd); +} + +/* + * Return a pointer to the LWP lwpsinfo structure. + * Clients should not hold on to this pointer indefinitely. + * It will become invalid on Lfree(). + */ +const lwpsinfo_t * +Lpsinfo(struct ps_lwphandle *L) +{ + if (Plwp_getpsinfo(L->lwp_proc, L->lwp_id, &L->lwp_psinfo) == -1) + return (NULL); + + return (&L->lwp_psinfo); +} + +/* + * Return a pointer to the LWP status structure. + * Clients should not hold on to this pointer indefinitely. + * It will become invalid on Lfree(). + */ +const lwpstatus_t * +Lstatus(struct ps_lwphandle *L) +{ + return (&L->lwp_status); +} + +/* + * Given an LWP handle, return the process handle. + */ +struct ps_prochandle * +Lprochandle(struct ps_lwphandle *L) +{ + return (L->lwp_proc); +} + +/* + * Ensure that all cached state is written to the LWP. + * The cached state is the LWP's signal mask and registers. + */ +void +Lsync(struct ps_lwphandle *L) +{ + int ctlfd = L->lwp_ctlfd; + long cmd[2]; + iovec_t iov[4]; + int n = 0; + + if (L->lwp_flags & SETHOLD) { + cmd[0] = PCSHOLD; + iov[n].iov_base = (caddr_t)&cmd[0]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&L->lwp_status.pr_lwphold; + iov[n++].iov_len = sizeof (L->lwp_status.pr_lwphold); + } + if (L->lwp_flags & SETREGS) { + cmd[1] = PCSREG; + iov[n].iov_base = (caddr_t)&cmd[1]; + iov[n++].iov_len = sizeof (long); + iov[n].iov_base = (caddr_t)&L->lwp_status.pr_reg[0]; + iov[n++].iov_len = sizeof (L->lwp_status.pr_reg); + } + + if (n == 0 || writev(ctlfd, iov, n) < 0) + return; /* nothing to do or write failed */ + + L->lwp_flags &= ~(SETHOLD|SETREGS); +} + +/* + * Wait for the specified LWP to stop or terminate. + * Or, just get the current status (PCNULL). + * Or, direct it to stop and get the current status (PCDSTOP). + */ +static int +Lstopstatus(struct ps_lwphandle *L, + long request, /* PCNULL, PCDSTOP, PCSTOP, PCWSTOP */ + uint_t msec) /* if non-zero, timeout in milliseconds */ +{ + int ctlfd = L->lwp_ctlfd; + long ctl[3]; + ssize_t rc; + int err; + + switch (L->lwp_state) { + case PS_RUN: + break; + case PS_STOP: + if (request != PCNULL && request != PCDSTOP) + return (0); + break; + case PS_LOST: + if (request != PCNULL) { + errno = EAGAIN; + return (-1); + } + break; + case PS_UNDEAD: + case PS_DEAD: + if (request != PCNULL) { + errno = ENOENT; + return (-1); + } + break; + default: /* corrupted state */ + dprintf("Lstopstatus: corrupted state: %d\n", L->lwp_state); + errno = EINVAL; + return (-1); + } + + ctl[0] = PCDSTOP; + ctl[1] = PCTWSTOP; + ctl[2] = (long)msec; + rc = 0; + switch (request) { + case PCSTOP: + rc = write(ctlfd, &ctl[0], 3*sizeof (long)); + break; + case PCWSTOP: + rc = write(ctlfd, &ctl[1], 2*sizeof (long)); + break; + case PCDSTOP: + rc = write(ctlfd, &ctl[0], 1*sizeof (long)); + break; + case PCNULL: + if (L->lwp_state == PS_DEAD) + return (0); /* Nothing else to do for cores */ + break; + default: /* programming error */ + errno = EINVAL; + return (-1); + } + err = (rc < 0)? errno : 0; + Lsync(L); + + if (pread(L->lwp_statfd, &L->lwp_status, + sizeof (L->lwp_status), (off_t)0) < 0) + err = errno; + + if (err) { + switch (err) { + case EINTR: /* user typed ctl-C */ + case ERESTART: + dprintf("Lstopstatus: EINTR\n"); + break; + case EAGAIN: /* we lost control of the the process */ + dprintf("Lstopstatus: EAGAIN\n"); + L->lwp_state = PS_LOST; + errno = err; + return (-1); + default: + if (_libproc_debug) { + const char *errstr; + + switch (request) { + case PCNULL: + errstr = "Lstopstatus PCNULL"; break; + case PCSTOP: + errstr = "Lstopstatus PCSTOP"; break; + case PCDSTOP: + errstr = "Lstopstatus PCDSTOP"; break; + case PCWSTOP: + errstr = "Lstopstatus PCWSTOP"; break; + default: + errstr = "Lstopstatus PC???"; break; + } + dprintf("%s: %s\n", errstr, strerror(err)); + } + L->lwp_state = PS_UNDEAD; + errno = err; + return (-1); + } + } + + if ((L->lwp_status.pr_flags & (PR_STOPPED|PR_ISTOP)) + != (PR_STOPPED|PR_ISTOP)) { + L->lwp_state = PS_RUN; + if (request == PCNULL || request == PCDSTOP || msec != 0) + return (0); + dprintf("Lstopstatus: LWP is not stopped\n"); + errno = EPROTO; + return (-1); + } + + L->lwp_state = PS_STOP; + + if (_libproc_debug) /* debugging */ + prldump("Lstopstatus", &L->lwp_status); + + switch (L->lwp_status.pr_why) { + case PR_SYSENTRY: + case PR_SYSEXIT: + case PR_REQUESTED: + case PR_SIGNALLED: + case PR_FAULTED: + case PR_JOBCONTROL: + case PR_SUSPENDED: + break; + default: + errno = EPROTO; + return (-1); + } + + return (0); +} + +/* + * Wait for the LWP to stop for any reason. + */ +int +Lwait(struct ps_lwphandle *L, uint_t msec) +{ + return (Lstopstatus(L, PCWSTOP, msec)); +} + +/* + * Direct the LWP to stop; wait for it to stop. + */ +int +Lstop(struct ps_lwphandle *L, uint_t msec) +{ + return (Lstopstatus(L, PCSTOP, msec)); +} + +/* + * Direct the LWP to stop; don't wait. + */ +int +Ldstop(struct ps_lwphandle *L) +{ + return (Lstopstatus(L, PCDSTOP, 0)); +} + +/* + * Get the value of one register from stopped LWP. + */ +int +Lgetareg(struct ps_lwphandle *L, int regno, prgreg_t *preg) +{ + if (regno < 0 || regno >= NPRGREG) { + errno = EINVAL; + return (-1); + } + + if (L->lwp_state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + *preg = L->lwp_status.pr_reg[regno]; + return (0); +} + +/* + * Put value of one register into stopped LWP. + */ +int +Lputareg(struct ps_lwphandle *L, int regno, prgreg_t reg) +{ + if (regno < 0 || regno >= NPRGREG) { + errno = EINVAL; + return (-1); + } + + if (L->lwp_state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + L->lwp_status.pr_reg[regno] = reg; + L->lwp_flags |= SETREGS; /* set registers before continuing */ + return (0); +} + +int +Lsetrun(struct ps_lwphandle *L, + int sig, /* signal to pass to LWP */ + int flags) /* PRSTEP|PRSABORT|PRSTOP|PRCSIG|PRCFAULT */ +{ + int ctlfd = L->lwp_ctlfd; + int sbits = (PR_DSTOP | PR_ISTOP | PR_ASLEEP); + + long ctl[1 + /* PCCFAULT */ + 1 + sizeof (siginfo_t)/sizeof (long) + /* PCSSIG/PCCSIG */ + 2 ]; /* PCRUN */ + + long *ctlp = ctl; + size_t size; + + if (L->lwp_state != PS_STOP && + (L->lwp_status.pr_flags & sbits) == 0) { + errno = EBUSY; + return (-1); + } + + Lsync(L); /* flush registers */ + + if (flags & PRCFAULT) { /* clear current fault */ + *ctlp++ = PCCFAULT; + flags &= ~PRCFAULT; + } + + if (flags & PRCSIG) { /* clear current signal */ + *ctlp++ = PCCSIG; + flags &= ~PRCSIG; + } else if (sig && sig != L->lwp_status.pr_cursig) { + /* make current signal */ + siginfo_t *infop; + + *ctlp++ = PCSSIG; + infop = (siginfo_t *)ctlp; + (void) memset(infop, 0, sizeof (*infop)); + infop->si_signo = sig; + ctlp += sizeof (siginfo_t) / sizeof (long); + } + + *ctlp++ = PCRUN; + *ctlp++ = flags; + size = (char *)ctlp - (char *)ctl; + + L->lwp_proc->info_valid = 0; /* will need to update map and file info */ + L->lwp_proc->state = PS_RUN; + L->lwp_state = PS_RUN; + + if (write(ctlfd, ctl, size) != size) { + /* Pretend that a job-stopped LWP is running */ + if (errno != EBUSY || L->lwp_status.pr_why != PR_JOBCONTROL) + return (Lstopstatus(L, PCNULL, 0)); + } + + return (0); +} + +int +Lclearsig(struct ps_lwphandle *L) +{ + int ctlfd = L->lwp_ctlfd; + long ctl = PCCSIG; + + if (write(ctlfd, &ctl, sizeof (ctl)) != sizeof (ctl)) + return (-1); + L->lwp_status.pr_cursig = 0; + return (0); +} + +int +Lclearfault(struct ps_lwphandle *L) +{ + int ctlfd = L->lwp_ctlfd; + long ctl = PCCFAULT; + + if (write(ctlfd, &ctl, sizeof (ctl)) != sizeof (ctl)) + return (-1); + return (0); +} + +/* + * Step over a breakpoint, i.e., execute the instruction that + * really belongs at the breakpoint location (the current %pc) + * and leave the LWP stopped at the next instruction. + */ +int +Lxecbkpt(struct ps_lwphandle *L, ulong_t saved) +{ + struct ps_prochandle *P = L->lwp_proc; + int rv, error; + + if (L->lwp_state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + Lsync(L); + error = execute_bkpt(L->lwp_ctlfd, + &P->status.pr_flttrace, &L->lwp_status.pr_lwphold, + L->lwp_status.pr_reg[R_PC], saved); + rv = Lstopstatus(L, PCNULL, 0); + + if (error != 0) { + if (L->lwp_status.pr_why == PR_JOBCONTROL && + error == EBUSY) { /* jobcontrol stop -- back off */ + L->lwp_state = PS_RUN; + return (0); + } + if (error == ENOENT) + return (0); + errno = error; + return (-1); + } + + return (rv); +} + +/* + * Step over a watchpoint, i.e., execute the instruction that was stopped by + * the watchpoint, and then leave the LWP stopped at the next instruction. + */ +int +Lxecwapt(struct ps_lwphandle *L, const prwatch_t *wp) +{ + struct ps_prochandle *P = L->lwp_proc; + int rv, error; + + if (L->lwp_state != PS_STOP) { + errno = EBUSY; + return (-1); + } + + Lsync(L); + error = execute_wapt(L->lwp_ctlfd, + &P->status.pr_flttrace, &L->lwp_status.pr_lwphold, wp); + rv = Lstopstatus(L, PCNULL, 0); + + if (error != 0) { + if (L->lwp_status.pr_why == PR_JOBCONTROL && + error == EBUSY) { /* jobcontrol stop -- back off */ + L->lwp_state = PS_RUN; + return (0); + } + if (error == ENOENT) + return (0); + errno = error; + return (-1); + } + + return (rv); +} + +int +Lstack(struct ps_lwphandle *L, stack_t *stkp) +{ + struct ps_prochandle *P = L->lwp_proc; + uintptr_t addr = L->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 +Lmain_stack(struct ps_lwphandle *L, stack_t *stkp) +{ + struct ps_prochandle *P = L->lwp_proc; + + if (Lstack(L, stkp) != 0) + return (-1); + + /* + * 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 (!(stkp->ss_flags & SS_ONSTACK)) + return (0); + + if (P->status.pr_dmodel == PR_MODEL_NATIVE) { + ucontext_t *ctxp = (void *)L->lwp_status.pr_oldcontext; + + if (Pread(P, stkp, sizeof (*stkp), + (uintptr_t)&ctxp->uc_stack) != sizeof (*stkp)) + return (-1); +#ifdef _LP64 + } else { + ucontext32_t *ctxp = (void *)L->lwp_status.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 +Lalt_stack(struct ps_lwphandle *L, stack_t *stkp) +{ + if (L->lwp_status.pr_altstack.ss_flags & SS_DISABLE) { + errno = ENODATA; + return (-1); + } + + *stkp = L->lwp_status.pr_altstack; + + return (0); +} + +/* + * Add a mapping to the given proc handle. Resizes the array as appropriate and + * manages reference counts on the given file_info_t. + * + * The 'map_relocate' member is used to tell Psort_mappings() that the + * associated file_map pointer needs to be relocated after the mappings have + * been sorted. It is only set for the first mapping, and has no meaning + * outside these two functions. + */ +int +Padd_mapping(struct ps_prochandle *P, off64_t off, file_info_t *fp, + prmap_t *pmap) +{ + map_info_t *mp; + + if (P->map_count == P->map_alloc) { + size_t next = P->map_alloc ? P->map_alloc * 2 : 16; + + if ((P->mappings = realloc(P->mappings, + next * sizeof (map_info_t))) == NULL) + return (-1); + + P->map_alloc = next; + } + + mp = &P->mappings[P->map_count++]; + + mp->map_offset = off; + mp->map_pmap = *pmap; + mp->map_relocate = 0; + if ((mp->map_file = fp) != NULL) { + if (fp->file_map == NULL) { + fp->file_map = mp; + mp->map_relocate = 1; + } + fp->file_ref++; + } + + return (0); +} + +static int +map_sort(const void *a, const void *b) +{ + const map_info_t *ap = a, *bp = b; + + if (ap->map_pmap.pr_vaddr < bp->map_pmap.pr_vaddr) + return (-1); + else if (ap->map_pmap.pr_vaddr > bp->map_pmap.pr_vaddr) + return (1); + else + return (0); +} + +/* + * Sort the current set of mappings. Should be called during target + * initialization after all calls to Padd_mapping() have been made. + */ +void +Psort_mappings(struct ps_prochandle *P) +{ + int i; + map_info_t *mp; + + qsort(P->mappings, P->map_count, sizeof (map_info_t), map_sort); + + /* + * Update all the file_map pointers to refer to the new locations. + */ + for (i = 0; i < P->map_count; i++) { + mp = &P->mappings[i]; + if (mp->map_relocate) + mp->map_file->file_map = mp; + mp->map_relocate = 0; + } +} |