2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A/*
2N/A * This file contains a set of generic routines for periodically
2N/A * sampling the state of another process, or tree of processes.
2N/A *
2N/A * It is built upon the infrastructure provided by libproc.
2N/A */
2N/A
2N/A#include <sys/wait.h>
2N/A#include <sys/syscall.h>
2N/A#include <sys/time.h>
2N/A#include <libproc.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <errno.h>
2N/A#include <unistd.h>
2N/A#include <signal.h>
2N/A#include <string.h>
2N/A#include <strings.h>
2N/A#include <limits.h>
2N/A#include <ctype.h>
2N/A#include <libintl.h>
2N/A#include <libcpc.h>
2N/A#include <sys/cpc_impl.h>
2N/A
2N/A#include "libpctx.h"
2N/A
2N/Astruct __pctx {
2N/A pctx_errfn_t *errfn;
2N/A struct ps_prochandle *Pr;
2N/A void *uarg;
2N/A pctx_sysc_execfn_t *exec;
2N/A pctx_sysc_forkfn_t *fork;
2N/A pctx_sysc_exitfn_t *exit;
2N/A pctx_sysc_lwp_createfn_t *lwp_create;
2N/A pctx_init_lwpfn_t *init_lwp;
2N/A pctx_fini_lwpfn_t *fini_lwp;
2N/A pctx_sysc_lwp_exitfn_t *lwp_exit;
2N/A int verbose;
2N/A int created;
2N/A int sigblocked;
2N/A int terminate;
2N/A sigset_t savedset;
2N/A cpc_t *cpc;
2N/A};
2N/A
2N/Astatic void (*pctx_cpc_callback)(cpc_t *cpc, struct __pctx *pctx);
2N/A
2N/Astatic void
2N/Apctx_default_errfn(const char *fn, const char *fmt, va_list ap)
2N/A{
2N/A (void) fprintf(stderr, "libpctx: pctx_%s: ", fn);
2N/A (void) vfprintf(stderr, fmt, ap);
2N/A}
2N/A
2N/A/*PRINTFLIKE3*/
2N/Astatic void
2N/Apctx_error(pctx_t *pctx, const char *fn, const char *fmt, ...)
2N/A{
2N/A va_list ap;
2N/A
2N/A va_start(ap, fmt);
2N/A pctx->errfn(fn, fmt, ap);
2N/A va_end(ap);
2N/A}
2N/A
2N/A/*
2N/A * Create a new process and bind the user args for it
2N/A */
2N/Apctx_t *
2N/Apctx_create(
2N/A const char *filename,
2N/A char *const *argv,
2N/A void *arg,
2N/A int verbose,
2N/A pctx_errfn_t *errfn)
2N/A{
2N/A static const char fn[] = "create";
2N/A int err;
2N/A pctx_t *pctx;
2N/A
2N/A pctx = calloc(1, sizeof (*pctx));
2N/A pctx->uarg = arg;
2N/A pctx->verbose = verbose;
2N/A pctx->terminate = 0;
2N/A pctx->errfn = errfn ? errfn : pctx_default_errfn;
2N/A
2N/A if ((pctx->Pr = Pcreate(filename, argv, &err, 0, 0)) == NULL) {
2N/A switch (err) {
2N/A case C_PERM:
2N/A pctx_error(pctx, fn, gettext("cannot trace set-id or "
2N/A "unreadable program '%s'\n"), filename);
2N/A break;
2N/A case C_LP64:
2N/A pctx_error(pctx, fn, gettext("cannot control LP64 "
2N/A "program '%s'\n"), filename);
2N/A break;
2N/A case C_NOEXEC:
2N/A pctx_error(pctx, fn, gettext("cannot execute "
2N/A "program '%s'\n"), filename);
2N/A break;
2N/A case C_NOENT:
2N/A pctx_error(pctx, fn, gettext("cannot find"
2N/A "program '%s'\n"), filename);
2N/A break;
2N/A case C_FORK:
2N/A pctx_error(pctx, fn, gettext("cannot fork, "
2N/A "program '%s'\n"), filename);
2N/A break;
2N/A default:
2N/A pctx_error(pctx, fn, gettext("%s, program '%s'\n"),
2N/A Pcreate_error(err), filename);
2N/A break;
2N/A }
2N/A free(pctx);
2N/A return (NULL);
2N/A }
2N/A
2N/A if (Psysentry(pctx->Pr, SYS_exit, 1) == -1) {
2N/A pctx_error(pctx, fn,
2N/A gettext("can't stop-on-exit() program '%s'\n"), filename);
2N/A Prelease(pctx->Pr, PRELEASE_KILL);
2N/A free(pctx);
2N/A return (NULL);
2N/A }
2N/A /*
2N/A * Set kill-on-last-close so the controlled process
2N/A * dies if we die.
2N/A */
2N/A pctx->created = 1;
2N/A (void) Psetflags(pctx->Pr, PR_KLC);
2N/A (void) pctx_set_events(pctx, PCTX_NULL_EVENT);
2N/A
2N/A return (pctx);
2N/A}
2N/A
2N/A/*
2N/A * Capture an existing process and bind the user args for it
2N/A */
2N/Apctx_t *
2N/Apctx_capture(pid_t pid, void *arg, int verbose, pctx_errfn_t *errfn)
2N/A{
2N/A static const char fn[] = "capture";
2N/A int err;
2N/A pctx_t *pctx;
2N/A
2N/A pctx = calloc(1, sizeof (*pctx));
2N/A pctx->uarg = arg;
2N/A pctx->verbose = verbose;
2N/A pctx->errfn = errfn ? errfn : pctx_default_errfn;
2N/A
2N/A if ((pctx->Pr = Pgrab(pid, 0, &err)) == NULL) {
2N/A switch (err) {
2N/A case G_NOPROC:
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d doesn't exist\n"), (int)pid);
2N/A break;
2N/A case G_ZOMB:
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d is a zombie\n"), (int)pid);
2N/A break;
2N/A case G_PERM:
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d: permission denied\n"), (int)pid);
2N/A break;
2N/A case G_BUSY:
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d is already being traced\n"),
2N/A (int)pid);
2N/A break;
2N/A case G_SYS:
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d is a system process\n"), (int)pid);
2N/A break;
2N/A case G_SELF:
2N/A pctx_error(pctx, fn,
2N/A gettext("cannot capture self!\n"));
2N/A break;
2N/A case G_LP64:
2N/A pctx_error(pctx, fn, gettext("cannot control LP64 "
2N/A "process, pid %d\n"), (int)pid);
2N/A break;
2N/A default:
2N/A pctx_error(pctx, fn, gettext("%s: pid %d\n"),
2N/A Pgrab_error(err), (int)pid);
2N/A break;
2N/A }
2N/A free(pctx);
2N/A return (NULL);
2N/A }
2N/A
2N/A if (Psysentry(pctx->Pr, SYS_exit, 1) == -1) {
2N/A pctx_error(pctx, fn,
2N/A gettext("can't stop-on-exit() pid %d\n"), (int)pid);
2N/A Prelease(pctx->Pr, PRELEASE_CLEAR);
2N/A free(pctx);
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * Set run-on-last-close so the controlled process
2N/A * runs even if we die on a signal. This is because
2N/A * we grabbed an existing process - it would be impolite
2N/A * to cause it to die if we exit prematurely.
2N/A */
2N/A pctx->created = 0;
2N/A (void) Psetflags(pctx->Pr, PR_RLC);
2N/A (void) pctx_set_events(pctx, PCTX_NULL_EVENT);
2N/A
2N/A return (pctx);
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Astatic void
2N/Adefault_void(pctx_t *pctx)
2N/A{}
2N/A
2N/A/*ARGSUSED*/
2N/Astatic int
2N/Adefault_int(pctx_t *pctx)
2N/A{
2N/A return (0);
2N/A}
2N/A
2N/Aint
2N/Apctx_set_events(pctx_t *pctx, ...)
2N/A{
2N/A static const char fn[] = "set_events";
2N/A va_list pvar;
2N/A int error = 0;
2N/A pctx_event_t event;
2N/A
2N/A va_start(pvar, pctx);
2N/A do {
2N/A switch (event = (pctx_event_t)va_arg(pvar, pctx_event_t)) {
2N/A case PCTX_NULL_EVENT:
2N/A break;
2N/A case PCTX_SYSC_EXEC_EVENT:
2N/A pctx->exec = (pctx_sysc_execfn_t *)
2N/A va_arg(pvar, pctx_sysc_execfn_t *);
2N/A break;
2N/A case PCTX_SYSC_FORK_EVENT:
2N/A pctx->fork = (pctx_sysc_forkfn_t *)
2N/A va_arg(pvar, pctx_sysc_forkfn_t *);
2N/A break;
2N/A case PCTX_SYSC_EXIT_EVENT: /* always intercepted */
2N/A pctx->exit = (pctx_sysc_exitfn_t *)
2N/A va_arg(pvar, pctx_sysc_exitfn_t *);
2N/A break;
2N/A case PCTX_SYSC_LWP_CREATE_EVENT:
2N/A pctx->lwp_create = (pctx_sysc_lwp_createfn_t *)
2N/A va_arg(pvar, pctx_sysc_lwp_createfn_t *);
2N/A break;
2N/A case PCTX_INIT_LWP_EVENT:
2N/A pctx->init_lwp = (pctx_init_lwpfn_t *)
2N/A va_arg(pvar, pctx_init_lwpfn_t *);
2N/A break;
2N/A case PCTX_FINI_LWP_EVENT:
2N/A pctx->fini_lwp = (pctx_fini_lwpfn_t *)
2N/A va_arg(pvar, pctx_fini_lwpfn_t *);
2N/A break;
2N/A case PCTX_SYSC_LWP_EXIT_EVENT:
2N/A pctx->lwp_exit = (pctx_sysc_lwp_exitfn_t *)
2N/A va_arg(pvar, pctx_sysc_lwp_exitfn_t *);
2N/A break;
2N/A default:
2N/A pctx_error(pctx, fn,
2N/A gettext("unknown event type %x\n"), event);
2N/A error = -1;
2N/A break;
2N/A }
2N/A } while (event != PCTX_NULL_EVENT && error == 0);
2N/A va_end(pvar);
2N/A
2N/A if (error != 0)
2N/A return (error);
2N/A
2N/A if (pctx->exec == NULL)
2N/A pctx->exec = (pctx_sysc_execfn_t *)default_int;
2N/A if (pctx->fork == NULL)
2N/A pctx->fork = (pctx_sysc_forkfn_t *)default_void;
2N/A if (pctx->exit == NULL)
2N/A pctx->exit = (pctx_sysc_exitfn_t *)default_void;
2N/A if (pctx->lwp_create == NULL)
2N/A pctx->lwp_create = (pctx_sysc_lwp_createfn_t *)default_int;
2N/A if (pctx->init_lwp == NULL)
2N/A pctx->init_lwp = (pctx_init_lwpfn_t *)default_int;
2N/A if (pctx->fini_lwp == NULL)
2N/A pctx->fini_lwp = (pctx_fini_lwpfn_t *)default_int;
2N/A if (pctx->lwp_exit == NULL)
2N/A pctx->lwp_exit = (pctx_sysc_lwp_exitfn_t *)default_int;
2N/A
2N/A if (pctx->fork != (pctx_sysc_forkfn_t *)default_void) {
2N/A (void) Psysexit(pctx->Pr, SYS_vfork, 1);
2N/A (void) Psysexit(pctx->Pr, SYS_forksys, 1);
2N/A if (Psetflags(pctx->Pr, PR_FORK) == -1)
2N/A error = -1;
2N/A } else {
2N/A (void) Psysexit(pctx->Pr, SYS_vfork, 0);
2N/A (void) Psysexit(pctx->Pr, SYS_forksys, 0);
2N/A if (Punsetflags(pctx->Pr, PR_FORK) == -1)
2N/A error = -1;
2N/A }
2N/A
2N/A /*
2N/A * exec causes termination of all but the exec-ing lwp,
2N/A * and resets the lwpid to one in the new address space.
2N/A */
2N/A if (pctx->exec != (pctx_sysc_execfn_t *)default_int ||
2N/A pctx->fini_lwp != (pctx_fini_lwpfn_t *)default_int ||
2N/A pctx->init_lwp != (pctx_init_lwpfn_t *)default_int) {
2N/A (void) Psysexit(pctx->Pr, SYS_execve, 1);
2N/A (void) Psysentry(pctx->Pr, SYS_execve, 1);
2N/A } else {
2N/A (void) Psysexit(pctx->Pr, SYS_execve, 0);
2N/A (void) Psysentry(pctx->Pr, SYS_execve, 0);
2N/A }
2N/A
2N/A (void) Psysexit(pctx->Pr, SYS_lwp_create,
2N/A pctx->lwp_create != (pctx_sysc_lwp_createfn_t *)default_int ||
2N/A pctx->init_lwp != (pctx_init_lwpfn_t *)default_int);
2N/A
2N/A (void) Psysentry(pctx->Pr, SYS_lwp_exit,
2N/A pctx->lwp_exit != (pctx_sysc_lwp_exitfn_t *)default_int ||
2N/A pctx->fini_lwp != (pctx_fini_lwpfn_t *)default_int);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic sigset_t termsig;
2N/A
2N/Astatic void
2N/A__libpctx_init(void)
2N/A{
2N/A /*
2N/A * Initialize the signal set used to shield ourselves from
2N/A * death-by-terminal-signal while the agent lwp is running.
2N/A */
2N/A (void) sigemptyset(&termsig);
2N/A (void) sigaddset(&termsig, SIGHUP);
2N/A (void) sigaddset(&termsig, SIGTERM);
2N/A (void) sigaddset(&termsig, SIGINT);
2N/A (void) sigaddset(&termsig, SIGQUIT);
2N/A}
2N/A
2N/A#pragma init(__libpctx_init)
2N/A
2N/Astatic void
2N/Apctx_begin_syscalls(pctx_t *pctx)
2N/A{
2N/A if (pctx->Pr == NULL)
2N/A return;
2N/A if (pctx->sigblocked++ == 0) {
2N/A (void) sigprocmask(SIG_BLOCK, &termsig, &pctx->savedset);
2N/A (void) Pcreate_agent(pctx->Pr);
2N/A }
2N/A}
2N/A
2N/Astatic void
2N/Apctx_end_syscalls(pctx_t *pctx)
2N/A{
2N/A if (pctx->Pr == NULL)
2N/A return;
2N/A if (--pctx->sigblocked == 0) {
2N/A (void) Pdestroy_agent(pctx->Pr);
2N/A (void) sigprocmask(SIG_SETMASK, &pctx->savedset, NULL);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Iterate over the valid lwpids in the process, invoking the
2N/A * action function on each one.
2N/A */
2N/Astatic int
2N/Apctx_lwpiterate(pctx_t *pctx, int (*action)(pctx_t *, pid_t, id_t, void *))
2N/A{
2N/A const pstatus_t *pstatus;
2N/A char lstatus[64];
2N/A struct stat statb;
2N/A lwpstatus_t *lwps;
2N/A prheader_t *prh;
2N/A int fd, nlwp;
2N/A int ret = 0;
2N/A
2N/A if (action == (int (*)(pctx_t *, pid_t, id_t, void *))default_int)
2N/A return (0);
2N/A
2N/A pstatus = Pstatus(pctx->Pr);
2N/A if (pstatus->pr_nlwp <= 1) {
2N/A pctx_begin_syscalls(pctx);
2N/A ret = action(pctx, pstatus->pr_pid, 1, pctx->uarg);
2N/A pctx_end_syscalls(pctx);
2N/A return (ret);
2N/A }
2N/A
2N/A (void) snprintf(lstatus, sizeof (lstatus),
2N/A "/proc/%d/lstatus", (int)pstatus->pr_pid);
2N/A
2N/A if ((fd = open(lstatus, O_RDONLY)) < 0 ||
2N/A fstat(fd, &statb) != 0) {
2N/A if (fd >= 0)
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A
2N/A prh = malloc(statb.st_size);
2N/A if (read(fd, prh, statb.st_size) <
2N/A sizeof (prheader_t) + sizeof (lwpstatus_t)) {
2N/A (void) close(fd);
2N/A free(prh);
2N/A return (-1);
2N/A }
2N/A (void) close(fd);
2N/A
2N/A /* LINTED pointer cast may result in improper alignment */
2N/A lwps = (lwpstatus_t *)(prh + 1);
2N/A pctx_begin_syscalls(pctx);
2N/A for (nlwp = prh->pr_nent; nlwp > 0; nlwp--) {
2N/A if (action(pctx,
2N/A pstatus->pr_pid, lwps->pr_lwpid, pctx->uarg) != 0)
2N/A ret = -1;
2N/A /* LINTED pointer cast may result in improper alignment */
2N/A lwps = (lwpstatus_t *)((char *)lwps + prh->pr_entsize);
2N/A }
2N/A pctx_end_syscalls(pctx);
2N/A free(prh);
2N/A return (ret);
2N/A}
2N/A
2N/A/*
2N/A * Free any associated state, but leave the process stopped if it
2N/A * is still under our control. (If it isn't under our control,
2N/A * it should just run to completion when we do our last close)
2N/A */
2N/Astatic void
2N/Apctx_free(pctx_t *pctx)
2N/A{
2N/A if (pctx->cpc != NULL && pctx_cpc_callback != NULL)
2N/A (*pctx_cpc_callback)(pctx->cpc, pctx);
2N/A if (pctx->Pr) {
2N/A Pfree(pctx->Pr);
2N/A pctx->Pr = NULL;
2N/A }
2N/A pctx->errfn = pctx_default_errfn;
2N/A}
2N/A
2N/A/*
2N/A * Completely release the process from our control and discard all our state
2N/A */
2N/Avoid
2N/Apctx_release(pctx_t *pctx)
2N/A{
2N/A if (pctx->Pr) {
2N/A Prelease(pctx->Pr, PRELEASE_CLEAR);
2N/A pctx->Pr = NULL;
2N/A }
2N/A
2N/A pctx_free(pctx);
2N/A bzero(pctx, sizeof (*pctx));
2N/A free(pctx);
2N/A}
2N/A
2N/Astatic void
2N/Amsincr(struct timeval *tv, uint_t msec)
2N/A{
2N/A tv->tv_sec += msec / MILLISEC;
2N/A tv->tv_usec += (msec % MILLISEC) * MILLISEC;
2N/A if (tv->tv_usec > MICROSEC) {
2N/A tv->tv_sec++;
2N/A tv->tv_usec -= MICROSEC;
2N/A }
2N/A}
2N/A
2N/Astatic uint_t
2N/Amsdiff(struct timeval *tva, struct timeval *tvb)
2N/A{
2N/A time_t sdiff = tva->tv_sec - tvb->tv_sec;
2N/A suseconds_t udiff = tva->tv_usec - tvb->tv_usec;
2N/A
2N/A if (sdiff < 0)
2N/A return (0);
2N/A if (udiff < 0) {
2N/A udiff += MICROSEC;
2N/A sdiff--;
2N/A }
2N/A if (sdiff < 0)
2N/A return (0);
2N/A if (sdiff >= (INT_MAX / MILLISEC))
2N/A return ((uint_t)INT_MAX);
2N/A return ((uint_t)(sdiff * MILLISEC + udiff / MILLISEC));
2N/A}
2N/A
2N/Aint
2N/Apctx_run(
2N/A pctx_t *pctx,
2N/A uint_t msec,
2N/A uint_t nsamples,
2N/A int (*tick)(pctx_t *, pid_t, id_t, void *))
2N/A{
2N/A static const char fn[] = "run";
2N/A struct timeval tvgoal, tvnow;
2N/A uint_t mswait = 0;
2N/A int running = 1;
2N/A const pstatus_t *pstatus;
2N/A psinfo_t psinfo;
2N/A void (*sigsaved)();
2N/A id_t lwpid;
2N/A pid_t pid = Pstatus(pctx->Pr)->pr_pid;
2N/A int pstate;
2N/A
2N/A if (msec == 0)
2N/A nsamples = 0;
2N/A if (nsamples == 0)
2N/A nsamples = UINT_MAX;
2N/A
2N/A /*
2N/A * Casually discard any knowledge of the children we create
2N/A */
2N/A sigsaved = signal(SIGCHLD, SIG_IGN);
2N/A
2N/A /*
2N/A * Since we've just "discovered" this process which might have
2N/A * been running for weeks, deliver some init_lwp events so
2N/A * that our caller gets a handle on the process.
2N/A */
2N/A if (pctx_lwpiterate(pctx, pctx->init_lwp) != 0) {
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("%d: lwp discovery failed\n"), (int)pid);
2N/A goto bailout;
2N/A }
2N/A
2N/A if (msec != 0) {
2N/A /*
2N/A * tvgoal represents the time at which the sample
2N/A * should next be taken.
2N/A */
2N/A (void) gettimeofday(&tvgoal, 0);
2N/A msincr(&tvgoal, msec);
2N/A }
2N/A
2N/A /*
2N/A * The event handling loop continues while running is 1.
2N/A * running becomes 0 when either the controlled process has
2N/A * exited successfully or the number of time samples has expired.
2N/A * Otherwise, if an error has occurred, running becomes -1.
2N/A */
2N/A while (running == 1 && !pctx->terminate) {
2N/A
2N/A if (Psetrun(pctx->Pr, 0, 0) != 0) {
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("%d: Psetrun\n"), (int)pid);
2N/A break;
2N/A }
2N/A
2N/A if (msec != 0) {
2N/A /*
2N/A * This timing loop attempts to estimate the number
2N/A * of milliseconds between our "goal" time (when
2N/A * we should stop the process and run the tick
2N/A * routine) and the current time.
2N/A *
2N/A * If we ever find ourselves running behind i.e. we
2N/A * missed our goal, then we skip ahead to the next
2N/A * goal instead.
2N/A */
2N/A do {
2N/A (void) gettimeofday(&tvnow, 0);
2N/A if ((mswait = msdiff(&tvgoal, &tvnow)) == 0) {
2N/A msincr(&tvgoal, msec);
2N/A /*
2N/A * Skip ahead to the next goal, unless
2N/A * there is only one more sample left
2N/A * to take.
2N/A */
2N/A if (nsamples != 1)
2N/A nsamples--;
2N/A }
2N/A } while (mswait == 0 && !pctx->terminate);
2N/A }
2N/A
2N/A if (pctx->terminate)
2N/A goto bailout;
2N/A else
2N/A (void) Pwait(pctx->Pr, mswait);
2N/A
2N/Acheckstate:
2N/A switch (pstate = Pstate(pctx->Pr)) {
2N/A case PS_RUN:
2N/A /*
2N/A * Try again, but wait for up to 5 seconds.
2N/A */
2N/A if (Pstop(pctx->Pr, 5 * MILLISEC) == -1 ||
2N/A (pstate = Pstate(pctx->Pr)) != PS_STOP) {
2N/A pctx_error(pctx, fn,
2N/A gettext("%d: won't stop\n"), (int)pid);
2N/A }
2N/A break;
2N/A case PS_STOP:
2N/A break;
2N/A case PS_LOST:
2N/A /*
2N/A * Lost control - probably execed a setuid/setgid
2N/A * executable. Try and get control back again,
2N/A * else bail ..
2N/A */
2N/A (void) Preopen(pctx->Pr);
2N/A if ((pstate = Pstate(pctx->Pr)) != PS_LOST)
2N/A goto checkstate;
2N/A pctx_error(pctx, fn,
2N/A gettext("%d: execed a program that cannot "
2N/A "be tracked\n"), (int)pid);
2N/A running = -1;
2N/A break;
2N/A case PS_UNDEAD:
2N/A case PS_DEAD:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("%d: process terminated\n"),
2N/A (int)pid);
2N/A running = -1;
2N/A break;
2N/A default:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("%d: process state 0x%x?\n"),
2N/A (int)pid, pstate);
2N/A break;
2N/A }
2N/A
2N/A if (pstate != PS_STOP)
2N/A break;
2N/A
2N/A pstatus = Pstatus(pctx->Pr);
2N/A lwpid = pstatus->pr_lwp.pr_lwpid;
2N/A switch (pstatus->pr_lwp.pr_why) {
2N/A case PR_REQUESTED:
2N/A msincr(&tvgoal, msec);
2N/A if (pstatus->pr_flags & PR_VFORKP) {
2N/A /*
2N/A * The process is in a vfork stupor until
2N/A * its child releases it via an exec.
2N/A * Don't sample it while it's in this state
2N/A * - we won't be able to create the agent.
2N/A */
2N/A break;
2N/A }
2N/A if (pctx_lwpiterate(pctx, tick) != 0)
2N/A running = -1;
2N/A if (running == 1 && --nsamples == 0)
2N/A running = 0;
2N/A break;
2N/A case PR_SYSENTRY:
2N/A switch (pstatus->pr_lwp.pr_what) {
2N/A case SYS_lwp_exit:
2N/A pctx_begin_syscalls(pctx);
2N/A (void) pctx->fini_lwp(pctx,
2N/A pid, lwpid, pctx->uarg);
2N/A (void) pctx->lwp_exit(pctx,
2N/A pid, lwpid, pctx->uarg);
2N/A pctx_end_syscalls(pctx);
2N/A break;
2N/A case SYS_exit:
2N/A if (pctx_lwpiterate(pctx, pctx->fini_lwp)
2N/A != 0)
2N/A running = -1;
2N/A pctx->exit(pctx, pid, lwpid,
2N/A (int)pstatus->pr_lwp.pr_sysarg[0],
2N/A pctx->uarg);
2N/A if (running == 1)
2N/A running = 0;
2N/A break;
2N/A case SYS_execve:
2N/A (void) pctx_lwpiterate(pctx, pctx->fini_lwp);
2N/A break;
2N/A default:
2N/A pctx_error(pctx, fn,
2N/A "warning - pid %d sysentry(%d)\n",
2N/A (int)pid, pstatus->pr_lwp.pr_what);
2N/A break;
2N/A }
2N/A break;
2N/A case PR_SYSEXIT:
2N/A switch (pstatus->pr_lwp.pr_what) {
2N/A case SYS_execve:
2N/A if (pstatus->pr_lwp.pr_errno) {
2N/A /*
2N/A * The exec failed completely.
2N/A * Reinstate the lwps we fini'd
2N/A * at exec entrance
2N/A */
2N/A if (pctx_lwpiterate(pctx,
2N/A pctx->init_lwp) == 0)
2N/A running = 1;
2N/A else
2N/A running = -1;
2N/A break;
2N/A }
2N/A if (pctx->exec == (pctx_sysc_execfn_t *)
2N/A default_int) {
2N/A running = 0;
2N/A break;
2N/A }
2N/A (void) memcpy(&psinfo,
2N/A Ppsinfo(pctx->Pr), sizeof (psinfo));
2N/A proc_unctrl_psinfo(&psinfo);
2N/A pctx_begin_syscalls(pctx);
2N/A if (pctx->exec(pctx, pid, lwpid,
2N/A psinfo.pr_psargs, pctx->uarg) != 0)
2N/A running = -1;
2N/A if (running == 1 && pctx->init_lwp(pctx,
2N/A pid, 1, pctx->uarg) != 0)
2N/A running = -1;
2N/A pctx_end_syscalls(pctx);
2N/A break;
2N/A case SYS_lwp_create:
2N/A if (pstatus->pr_lwp.pr_errno ||
2N/A pstatus->pr_lwp.pr_rval1)
2N/A break;
2N/A pctx_begin_syscalls(pctx);
2N/A if (pctx->init_lwp(pctx, pid, lwpid,
2N/A pctx->uarg) != 0)
2N/A running = -1;
2N/A if (running == 1 && pctx->lwp_create(pctx,
2N/A pid, lwpid, pctx->uarg) != 0)
2N/A running = -1;
2N/A pctx_end_syscalls(pctx);
2N/A break;
2N/A case SYS_vfork:
2N/A case SYS_forksys:
2N/A if (pstatus->pr_lwp.pr_errno)
2N/A break;
2N/A (void) fflush(NULL);
2N/A switch (fork1()) {
2N/A pid_t ppid;
2N/A int wascreated;
2N/A pctx_sysc_forkfn_t *forkfn;
2N/A case 0:
2N/A ppid = pid;
2N/A pid = pstatus->pr_lwp.pr_rval1;
2N/A wascreated = pctx->created;
2N/A forkfn = pctx->fork;
2N/A pctx_free(pctx);
2N/A pctx = pctx_capture(pid, pctx->uarg,
2N/A pctx->verbose, pctx->errfn);
2N/A if (pctx != NULL) {
2N/A if (wascreated) {
2N/A /*
2N/A * Set kill on last
2N/A * close so -all-
2N/A * children die.
2N/A */
2N/A pctx->created = 1;
2N/A (void) Psetflags(
2N/A pctx->Pr, PR_KLC);
2N/A }
2N/A (*forkfn)(pctx, ppid, pid,
2N/A lwpid, pctx->uarg);
2N/A pctx_release(pctx);
2N/A _exit(0);
2N/A } else {
2N/A _exit(1);
2N/A }
2N/A /*NOTREACHED*/
2N/A case -1:
2N/A pctx_error(pctx, fn,
2N/A "cannot follow pid %d: %s\n",
2N/A (int)pstatus->pr_lwp.pr_rval1,
2N/A strerror(errno));
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A break;
2N/A default:
2N/A pctx_error(pctx, fn, gettext(
2N/A "warning - pid %d sysexit(%d)\n"),
2N/A (int)pid, pstatus->pr_lwp.pr_what);
2N/A break;
2N/A }
2N/A break;
2N/A case PR_SIGNALLED:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d - signalled\n"), (int)pid);
2N/A break;
2N/A case PR_JOBCONTROL:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d - job control stop\n"),
2N/A (int)pid);
2N/A running = -1;
2N/A break;
2N/A case PR_FAULTED:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d - faulted\n"), (int)pid);
2N/A break;
2N/A case PR_SUSPENDED:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d - suspended\n"), (int)pid);
2N/A break;
2N/A case PR_CHECKPOINT:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d - checkpoint\n"),
2N/A (int)pid);
2N/A break;
2N/A default:
2N/A if (pctx->verbose)
2N/A pctx_error(pctx, fn,
2N/A gettext("pid %d - reason %d\n"),
2N/A (int)pid, pstatus->pr_lwp.pr_why);
2N/A running = -1;
2N/A break;
2N/A }
2N/A }
2N/A
2N/Abailout:
2N/A (void) signal(SIGCHLD, sigsaved);
2N/A
2N/A if (pctx->terminate)
2N/A return (0);
2N/A
2N/A switch (running) {
2N/A case 0:
2N/A return (0);
2N/A case -1:
2N/A return (-1);
2N/A default:
2N/A pctx_error(pctx, fn, gettext("lost control of pid %d\n"),
2N/A (int)pid);
2N/A pctx_free(pctx);
2N/A return (-1);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Execute the private 'cpc' system call in the context of the
2N/A * controlled process.
2N/A */
2N/Aint
2N/A__pctx_cpc(pctx_t *pctx, cpc_t *cpc,
2N/A int cmd, id_t lwpid, void *data1, void *data2, void *data3, int bufsize)
2N/A{
2N/A sysret_t rval;
2N/A argdes_t argd[5];
2N/A argdes_t *adp = &argd[0];
2N/A int error;
2N/A
2N/A /*
2N/A * Keep track of the relationship between cpc_t and pctx_t here.
2N/A * We store the last cpc_t used by libpctx, so that when this pctx is
2N/A * destroyed, libpctx can notify libcpc.
2N/A */
2N/A
2N/A if (pctx->cpc != NULL && pctx->cpc != cpc && pctx_cpc_callback != NULL)
2N/A (*pctx_cpc_callback)(pctx->cpc, pctx);
2N/A pctx->cpc = cpc;
2N/A
2N/A /*
2N/A * cmd and lwpid are passed in by value no matter what the command is.
2N/A */
2N/A adp->arg_value = cmd;
2N/A adp->arg_object = NULL;
2N/A adp->arg_type = AT_BYVAL;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = 0;
2N/A adp++;
2N/A
2N/A adp->arg_value = lwpid;
2N/A adp->arg_object = NULL;
2N/A adp->arg_type = AT_BYVAL;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = 0;
2N/A adp++;
2N/A
2N/A switch (cmd) {
2N/A case CPC_BIND:
2N/A adp->arg_value = 0;
2N/A adp->arg_object = data1;
2N/A adp->arg_type = AT_BYREF;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = (size_t)data2;
2N/A adp++;
2N/A
2N/A adp->arg_value = (size_t)data2;
2N/A adp->arg_object = NULL;
2N/A adp->arg_type = AT_BYVAL;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = 0;
2N/A adp++;
2N/A
2N/A adp->arg_value = 0;
2N/A adp->arg_object = data3;
2N/A adp->arg_type = AT_BYREF;
2N/A adp->arg_inout = AI_INOUT;
2N/A adp->arg_size = sizeof (int);
2N/A
2N/A break;
2N/A case CPC_SAMPLE:
2N/A adp->arg_value = 0;
2N/A adp->arg_object = data1;
2N/A adp->arg_type = AT_BYREF;
2N/A adp->arg_inout = AI_OUTPUT;
2N/A adp->arg_size = bufsize;
2N/A adp++;
2N/A
2N/A adp->arg_value = 0;
2N/A adp->arg_object = data2;
2N/A adp->arg_type = AT_BYREF;
2N/A adp->arg_inout = AI_OUTPUT;
2N/A adp->arg_size = sizeof (hrtime_t);
2N/A adp++;
2N/A
2N/A adp->arg_value = 0;
2N/A adp->arg_object = data3;
2N/A adp->arg_type = AT_BYREF;
2N/A adp->arg_inout = AI_OUTPUT;
2N/A adp->arg_size = sizeof (uint64_t);
2N/A
2N/A break;
2N/A default:
2N/A adp->arg_value = 0;
2N/A adp->arg_object = 0;
2N/A adp->arg_type = AT_BYVAL;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = 0;
2N/A adp++;
2N/A
2N/A adp->arg_value = 0;
2N/A adp->arg_object = 0;
2N/A adp->arg_type = AT_BYVAL;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = 0;
2N/A adp++;
2N/A
2N/A adp->arg_value = 0;
2N/A adp->arg_object = 0;
2N/A adp->arg_type = AT_BYVAL;
2N/A adp->arg_inout = AI_INPUT;
2N/A adp->arg_size = 0;
2N/A
2N/A break;
2N/A }
2N/A
2N/A error = Psyscall(pctx->Pr, &rval, SYS_cpc, 5, &argd[0]);
2N/A
2N/A if (error) {
2N/A errno = error > 0 ? error : ENOSYS;
2N/A return (-1);
2N/A }
2N/A return (rval.sys_rval1);
2N/A}
2N/A
2N/A/*
2N/A * libcpc-private hook used to register a callback. The callback is used to
2N/A * notify libcpc when a pctx handle is invalidated.
2N/A */
2N/Avoid
2N/A__pctx_cpc_register_callback(void (*arg)(struct __cpc *, struct __pctx *))
2N/A{
2N/A pctx_cpc_callback = arg;
2N/A}
2N/A
2N/A/*
2N/A * Tell pctx_run to bail out immediately
2N/A */
2N/Avoid
2N/Apctx_terminate(struct __pctx *pctx)
2N/A{
2N/A pctx->terminate = 1;
2N/A}