OpenSolaris_b135/lib/libproc/common/Psyscall.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#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"
#include "Pisadep.h"

extern sigset_t blockable_sigs;

static void
Pabort_agent(struct ps_prochandle *P)
{
	int sysnum = P->status.pr_lwp.pr_syscall;
	int stop;

	dprintf("agent LWP is asleep in syscall %d\n", sysnum);
	(void) Pstop(P, 0);
	stop = Psysexit(P, sysnum, TRUE);

	if (Psetrun(P, 0, PRSABORT) == 0) {
		while (Pwait(P, 0) == -1 && errno == EINTR)
			continue;
		(void) Psysexit(P, sysnum, stop);
		dprintf("agent LWP system call aborted\n");
	}
}

/*
 * Create the /proc agent LWP for further operations.
 */
int
Pcreate_agent(struct ps_prochandle *P)
{
	int fd;
	char pathname[PATH_MAX];
	char *fname;
	struct {
		long	cmd;
		prgregset_t regs;
	} cmd;

	/*
	 * If not first reference, we already have the /proc agent LWP active.
	 */
	if (P->agentcnt > 0) {
		P->agentcnt++;
		return (0);
	}

	/*
	 * The agent is not available for use as a mortician or as an
	 * obstetrician.
	 */
	if (P->state == PS_DEAD || P->state == PS_UNDEAD ||
	    P->state == PS_IDLE) {
		errno = ENOENT;
		return (-1);
	}

	/*
	 * Create the special /proc agent LWP if it doesn't already exist.
	 * Give it the registers of the representative LWP.
	 */
	(void) Pstop(P, 0);
	Psync(P);
	if (!(P->status.pr_lwp.pr_flags & PR_AGENT)) {
		cmd.cmd = PCAGENT;
		(void) memcpy(&cmd.regs, &P->status.pr_lwp.pr_reg[0],
		    sizeof (P->status.pr_lwp.pr_reg));
		if (write(P->ctlfd, &cmd, sizeof (cmd)) != sizeof (cmd))
			goto bad;
	}

	/* refresh the process status */
	(void) Pstopstatus(P, PCNULL, 0);

	/* open the agent LWP files */
	(void) snprintf(pathname, sizeof (pathname), "%s/%d/lwp/agent/",
	    procfs_path, (int)P->pid);
	fname = pathname + strlen(pathname);
	(void) set_minfd();

	/*
	 * It is difficult to know how to recover from the two errors
	 * that follow.  The agent LWP exists and we need to kill it,
	 * but we can't because we need it active in order to kill it.
	 * We just hope that these failures never occur.
	 */
	(void) strcpy(fname, "lwpstatus");
	if ((fd = open(pathname, O_RDONLY)) < 0 ||
	    (fd = dupfd(fd, 0)) < 0)
		goto bad;
	P->agentstatfd = fd;

	(void) strcpy(fname, "lwpctl");
	if ((fd = open(pathname, O_WRONLY)) < 0 ||
	    (fd = dupfd(fd, 0)) < 0)
		goto bad;
	P->agentctlfd = fd;

	/*
	 * If the agent is currently asleep in a system call, attempt
	 * to abort the system call so it's ready to serve.
	 */
	if (P->status.pr_lwp.pr_flags & PR_ASLEEP) {
		dprintf("Pcreate_agent: aborting agent syscall\n");
		Pabort_agent(P);
	}

	/* get the agent LWP status */
	P->agentcnt++;
	if (Pstopstatus(P, PCNULL, 0) != 0) {
		Pdestroy_agent(P);
		return (-1);
	}

	return (0);

bad:
	if (P->agentstatfd >= 0)
		(void) close(P->agentstatfd);
	if (P->agentctlfd >= 0)
		(void) close(P->agentctlfd);
	P->agentstatfd = -1;
	P->agentctlfd = -1;
	/* refresh the process status */
	(void) Pstopstatus(P, PCNULL, 0);
	return (-1);
}

/*
 * Decrement the /proc agent agent reference count.
 * On last reference, destroy the agent.
 */
void
Pdestroy_agent(struct ps_prochandle *P)
{
	if (P->agentcnt > 1)
		P->agentcnt--;
	else {
		int flags;

		Psync(P); /* Flush out any pending changes */

		(void) Pstopstatus(P, PCNULL, 0);
		flags = P->status.pr_lwp.pr_flags;

		/*
		 * If the agent is currently asleep in a system call, attempt
		 * to abort the system call so we can terminate the agent.
		 */
		if ((flags & (PR_AGENT|PR_ASLEEP)) == (PR_AGENT|PR_ASLEEP)) {
			dprintf("Pdestroy_agent: aborting agent syscall\n");
			Pabort_agent(P);
		}

		/*
		 * The agent itself is destroyed by forcing it to execute
		 * the _lwp_exit(2) system call.  Close our agent descriptors
		 * regardless of whether this is successful.
		 */
		(void) pr_lwp_exit(P);
		(void) close(P->agentctlfd);
		(void) close(P->agentstatfd);
		P->agentctlfd = -1;
		P->agentstatfd = -1;
		P->agentcnt = 0;

		/*
		 * Now that (hopefully) the agent has exited, refresh the
		 * status: the representative LWP is no longer the agent.
		 */
		(void) Pstopstatus(P, PCNULL, 0);
	}
}

/*
 * Execute the syscall instruction.
 */
static int
execute(struct ps_prochandle *P, int sysindex)
{
	int ctlfd = (P->agentctlfd >= 0)? P->agentctlfd : P->ctlfd;
	int washeld = FALSE;
	sigset_t hold;		/* mask of held signals */
	int cursig;
	struct {
		long cmd;
		siginfo_t siginfo;
	} ctl;
	int sentry;		/* old value of stop-on-syscall-entry */

	sentry = Psysentry(P, sysindex, TRUE);	/* set stop-on-syscall-entry */

	/*
	 * If not already blocked, block all signals now.
	 */
	if (memcmp(&P->status.pr_lwp.pr_lwphold, &blockable_sigs,
	    sizeof (sigset_t)) != 0) {
		hold = P->status.pr_lwp.pr_lwphold;
		P->status.pr_lwp.pr_lwphold = blockable_sigs;
		P->flags |= SETHOLD;
		washeld = TRUE;
	}

	/*
	 * If there is a current signal, remember it and cancel it.
	 */
	if ((cursig = P->status.pr_lwp.pr_cursig) != 0) {
		ctl.cmd = PCSSIG;
		ctl.siginfo = P->status.pr_lwp.pr_info;
	}

	if (Psetrun(P, 0, PRCSIG | PRCFAULT) == -1)
		goto bad;

	while (P->state == PS_RUN) {
		(void) Pwait(P, 0);
	}
	if (P->state != PS_STOP)
		goto bad;

	if (cursig)				/* restore cursig */
		(void) write(ctlfd, &ctl, sizeof (ctl));
	if (washeld) {		/* restore the signal mask if we set it */
		P->status.pr_lwp.pr_lwphold = hold;
		P->flags |= SETHOLD;
	}

	(void) Psysentry(P, sysindex, sentry);	/* restore sysentry stop */

	if (P->status.pr_lwp.pr_why  == PR_SYSENTRY &&
	    P->status.pr_lwp.pr_what == sysindex)
		return (0);
bad:
	return (-1);
}


/*
 * Perform system call in controlled process.
 */
int
Psyscall(struct ps_prochandle *P,
	sysret_t *rval,		/* syscall return values */
	int sysindex,		/* system call index */
	uint_t nargs,		/* number of arguments to system call */
	argdes_t *argp)		/* argument descriptor array */
{
	int agent_created = FALSE;
	pstatus_t save_pstatus;
	argdes_t *adp;			/* pointer to argument descriptor */
	int i;				/* general index value */
	int model;			/* data model */
	int error = 0;			/* syscall errno */
	int Perr = 0;			/* local error number */
	int sexit;			/* old value of stop-on-syscall-exit */
	prgreg_t sp;			/* adjusted stack pointer */
	prgreg_t ap;			/* adjusted argument pointer */
	sigset_t unblock;

	(void) sigprocmask(SIG_BLOCK, &blockable_sigs, &unblock);

	rval->sys_rval1 = 0;		/* initialize return values */
	rval->sys_rval2 = 0;

	if (sysindex <= 0 || sysindex > PRMAXSYS || nargs > MAXARGS)
		goto bad1;	/* programming error */

	if (P->state == PS_DEAD || P->state == PS_UNDEAD || P->state == PS_IDLE)
		goto bad1;	/* dead processes can't perform system calls */

	model = P->status.pr_dmodel;
#ifndef _LP64
	/* We must be a 64-bit process to deal with a 64-bit process */
	if (model == PR_MODEL_LP64)
		goto bad9;
#endif

	/*
	 * Create the /proc agent LWP in the process to do all the work.
	 * (It may already exist; nested create/destroy is permitted
	 * by virtue of the reference count.)
	 */
	if (Pcreate_agent(P) != 0)
		goto bad8;

	/*
	 * Save agent's status to restore on exit.
	 */
	agent_created = TRUE;
	save_pstatus = P->status;

	if (P->state != PS_STOP ||		/* check state of LWP */
	    (P->status.pr_flags & PR_ASLEEP))
		goto bad2;

	if (Pscantext(P))			/* bad text ? */
		goto bad3;

	/*
	 * Validate arguments and compute the stack frame parameters.
	 * Begin with the current stack pointer.
	 */
#ifdef _LP64
	if (model == PR_MODEL_LP64) {
		sp = P->status.pr_lwp.pr_reg[R_SP] + STACK_BIAS;
		sp = PSTACK_ALIGN64(sp);
	} else {
#endif
		sp = (uint32_t)P->status.pr_lwp.pr_reg[R_SP];
		sp = PSTACK_ALIGN32(sp);
#ifdef _LP64
	}
#endif

	/*
	 * For each AT_BYREF argument, compute the necessary
	 * stack space and the object's stack address.
	 */
	for (i = 0, adp = argp; i < nargs; i++, adp++) {
		rval->sys_rval1 = i;		/* in case of error */
		switch (adp->arg_type) {
		default:			/* programming error */
			goto bad4;
		case AT_BYVAL:			/* simple argument */
			break;
		case AT_BYREF:			/* must allocate space */
			switch (adp->arg_inout) {
			case AI_INPUT:
			case AI_OUTPUT:
			case AI_INOUT:
				if (adp->arg_object == NULL)
					goto bad5;	/* programming error */
				break;
			default:		/* programming error */
				goto bad6;
			}
			/* allocate stack space for BYREF argument */
			if (adp->arg_size == 0 || adp->arg_size > MAXARGL)
				goto bad7;	/* programming error */
#ifdef _LP64
			if (model == PR_MODEL_LP64)
				sp = PSTACK_ALIGN64(sp - adp->arg_size);
			else
#endif
				sp = PSTACK_ALIGN32(sp - adp->arg_size);
			adp->arg_value = sp;	/* stack address for object */
			break;
		}
	}
	rval->sys_rval1 = 0;			/* in case of error */
	/*
	 * Point of no return.
	 * Perform the system call entry, adjusting %sp.
	 * This moves the LWP to the stopped-on-syscall-entry state
	 * just before the arguments to the system call are fetched.
	 */
	ap = Psyscall_setup(P, nargs, sysindex, sp);
	P->flags |= SETREGS;	/* set registers before continuing */
	dprintf("Psyscall(): execute(sysindex = %d)\n", sysindex);

	/*
	 * Execute the syscall instruction and stop on syscall entry.
	 */
	if (execute(P, sysindex) != 0 ||
	    (!Pissyscall(P, P->status.pr_lwp.pr_reg[R_PC]) &&
	    !Pissyscall_prev(P, P->status.pr_lwp.pr_reg[R_PC], NULL)))
		goto bad10;

	dprintf("Psyscall(): copying arguments\n");

	/*
	 * The LWP is stopped at syscall entry.
	 * Copy objects to stack frame for each argument.
	 */
	for (i = 0, adp = argp; i < nargs; i++, adp++) {
		rval->sys_rval1 = i;		/* in case of error */
		if (adp->arg_type != AT_BYVAL &&
		    adp->arg_inout != AI_OUTPUT) {
			/* copy input byref parameter to process */
			if (Pwrite(P, adp->arg_object, adp->arg_size,
			    (uintptr_t)adp->arg_value) != adp->arg_size)
				goto bad17;
		}
	}
	rval->sys_rval1 = 0;			/* in case of error */
	if (Psyscall_copyinargs(P, nargs, argp, ap) != 0)
		goto bad18;

	/*
	 * Complete the system call.
	 * This moves the LWP to the stopped-on-syscall-exit state.
	 */
	dprintf("Psyscall(): set running at sysentry\n");

	sexit = Psysexit(P, sysindex, TRUE);	/* catch this syscall exit */
	do {
		if (Psetrun(P, 0, 0) == -1)
			goto bad21;
		while (P->state == PS_RUN)
			(void) Pwait(P, 0);
	} while (P->state == PS_STOP && P->status.pr_lwp.pr_why != PR_SYSEXIT);
	(void) Psysexit(P, sysindex, sexit);	/* restore original setting */

	/*
	 * If the system call was _lwp_exit(), we expect that our last call
	 * to Pwait() will yield ENOENT because the LWP no longer exists.
	 */
	if (sysindex == SYS_lwp_exit && errno == ENOENT) {
		dprintf("Psyscall(): _lwp_exit successful\n");
		rval->sys_rval1 = rval->sys_rval2 = 0;
		goto out;
	}

	if (P->state != PS_STOP || P->status.pr_lwp.pr_why != PR_SYSEXIT)
		goto bad22;

	if (P->status.pr_lwp.pr_what != sysindex)
		goto bad23;

	if (!Pissyscall_prev(P, P->status.pr_lwp.pr_reg[R_PC], NULL)) {
		dprintf("Pissyscall_prev() failed\n");
		goto bad24;
	}

	dprintf("Psyscall(): caught at sysexit\n");

	/*
	 * For each argument.
	 */
	for (i = 0, adp = argp; i < nargs; i++, adp++) {
		rval->sys_rval1 = i;		/* in case of error */
		if (adp->arg_type != AT_BYVAL &&
		    adp->arg_inout != AI_INPUT) {
			/* copy output byref parameter from process */
			if (Pread(P, adp->arg_object, adp->arg_size,
			    (uintptr_t)adp->arg_value) != adp->arg_size)
				goto bad25;
		}
	}

	if (Psyscall_copyoutargs(P, nargs, argp, ap) != 0)
		goto bad26;

	/*
	 * Get the return values from the syscall.
	 */
	if (P->status.pr_lwp.pr_errno) {	/* error return */
		error = P->status.pr_lwp.pr_errno;
		rval->sys_rval1 = -1L;
		rval->sys_rval2 = -1L;
		dprintf("Psyscall(%d) fails with errno %d\n",
		    sysindex, error);
	} else {				/* normal return */
		rval->sys_rval1 = P->status.pr_lwp.pr_rval1;
		rval->sys_rval2 = P->status.pr_lwp.pr_rval2;
		dprintf("Psyscall(%d) returns 0x%lx 0x%lx\n", sysindex,
		    P->status.pr_lwp.pr_rval1, P->status.pr_lwp.pr_rval2);
	}

	goto out;

bad26:	Perr++;
bad25:	Perr++;
bad24:	Perr++;
bad23:	Perr++;
bad22:	Perr++;
bad21:	Perr++;
	Perr++;
	Perr++;
bad18:	Perr++;
bad17:	Perr++;
	Perr++;
	Perr++;
	Perr++;
	Perr++;
	Perr++;
	Perr++;
bad10:	Perr++;
bad9:	Perr++;
bad8:	Perr++;
bad7:	Perr++;
bad6:	Perr++;
bad5:	Perr++;
bad4:	Perr++;
bad3:	Perr++;
bad2:	Perr++;
bad1:	Perr++;
	error = -1;
	dprintf("Psyscall(%d) fails with local error %d\n", sysindex, Perr);

out:
	/*
	 * Destroy the /proc agent LWP now (or just bump down the ref count).
	 */
	if (agent_created) {
		if (P->state != PS_UNDEAD) {
			P->status = save_pstatus;
			P->flags |= SETREGS;
			Psync(P);
		}
		Pdestroy_agent(P);
	}

	(void) sigprocmask(SIG_SETMASK, &unblock, NULL);
	return (error);
}