/* $Header:process.c 12.0$ */
/* $ACIS:process.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ucb/dbx/RCS/process.c,v $ */

#ifndef lint
static char *rcsid = "$Header:process.c 12.0$";
#endif

/* Copyright (c) 1982 Regents of the University of California */

/*
 * Process management.
 *
 * This module contains the routines to manage the execution and
 * tracing of the debuggee process, using the ptrace system call.
 */

#include "defs.h"
#include "process.h"
#include "procinfo.h"
#include "execute.h"
#include "machine.h"
#include "coredump.h"
#include <signal.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <machine/float.h> 
#include <machine/reg.h>

#ifndef public

typedef struct Process *Process;

Process process;
int extendedfloats;

#include "machine.h"

#define NOTSTARTED 1
#define STOPPED 0177
#define FINISHED 0

/*
 * These definitions are for the arguments to "pio".
 */

typedef enum { PREAD, PWRITE } PioOp;
typedef enum { TEXTSEG, DATASEG } PioSeg;

#define setrep(n)       (1 << ((n)-1))

#endif

private struct Process pbuf;

extern int errno;

#define WMASK           (~(sizeof(Word) - 1))

#define FIRSTSIG        SIGINT
#define LASTSIG         SIGQUIT
#define ischild(pid)    ((pid) == 0)
#define istraced(p)     (p->sigset&setrep(p->signo))

/*
 * Initialize process information.
 */

public process_init ()
{
    register int i;
    register Process p;

    p = &pbuf;
    process = p;
    p->status = (coredump) ? STOPPED : NOTSTARTED;
    setsigtrace(p);
    defregsyms();
    if (coredump) {
	coredump_readin(p->reg, &p->signo);
	for (i = 0; i <= regword(NREG + NFLREG - 1); i++) {
	    p->valid[i] = 0xffffffff;
	    p->dirty[i] = 0;
	}
	pc = reg(PROGCTR);
    }
    arginit();
}

/*
 * Start up a new process by forking and exec-ing the
 * given argument list, returning when the process is loaded
 * and ready to execute.  The PROCESS information (pointed to
 * by the first argument) is appropriately filled.
 *
 * If the given PROCESS structure is associated with an already running
 * process, we terminate it.
 */

public pstart (p, argv, infile, outfile)
Process p;
String argv[];
String infile;
String outfile;
{
    int status;

    if (p->pid != 0) {
	pterm(p);
	cacheflush(p);
    }
    fflush(stdout);
    psigtrace(p, SIGTRAP, true);
    p->pid = vfork();
    if (p->pid == -1) {
	panic("can't fork");
    }
    if (ischild(p->pid)) {
	nocatcherrs();
	ptrace(PT_TRACE_ME, 0, 0, 0);
	if (infile != nil) {
	    infrom(infile);
	}
	if (outfile != nil) {
	    outto(outfile);
	}
	execv(argv[0], argv);
	_exit(1);
    }
    pwait(p->pid, &status);
    uinit(p);
    getinfo(p, status);
    adjustpc(p, PT_TRACE_ME);
    if (p->status != STOPPED) {
	beginerrmsg();
	fprintf(stderr, "warning: cannot execute %s\n", argv[0]);
    } else {
	ptraced(p->pid);
    }
}

/*
 * Terminate a ptrace'd process.
 */

public pterm (p)
Process p;
{
    int status;

    if (p != nil and p->pid != 0) {
	ptrace(PT_KILL, p->pid, 0, 0);
	pwait(p->pid, &status);
	unptraced(p->pid);
    }
}

/*
 * Continue a stopped process.  The first argument points to a Process
 * structure.  Before the process is restarted it's user area is modified
 * according to the values in the structure.  When this routine finishes,
 * the structure has the new values from the process's user area.
 *
 * Pcont terminates when the process stops with a signal pending that
 * is being traced (via psigtrace), or when the process terminates.
 */

public pcont (p, signo)
Process p;
int signo;
{
    int s, status;

    if (p->pid == 0) {
	error("program is not active");
    }
    s = signo;
    do {
	setinfo(p, s);
	if (traceexec) {
	    printf("!! pcont from 0x%x with signal %d (%d)\n",
		reg(PROGCTR), s, p->signo);
	    fflush(stdout);
	}
	sigs_off();
	if (ptrace(PT_CONTINUE, p->pid, reg(PROGCTR), p->signo) < 0) {
	    panic("error %d trying to continue process", errno);
	}
	pwait(p->pid, &status);
	sigs_on();
	getinfo(p, status);
	adjustpc(p, PT_CONTINUE);
	if (p->status == STOPPED and traceexec and not istraced(p)) {
	    printf("!! ignored signal %d at 0x%x\n",
		p->signo, reg(PROGCTR));
	    fflush(stdout);
	}
	s = p->signo;
    } while (p->status == STOPPED and not istraced(p));
    if (traceexec) {
	printf("!! pcont to 0x%x on signal %d\n", reg(PROGCTR), p->signo);
	fflush(stdout);
    }
}

/*
 * Single step as best ptrace can.
 */

public pstep (p, signo)
Process p;
int signo;
{
    int s, status;

    s = signo;
    do {
	setinfo(p, s);
	if (traceexec) {
	    printf("!! pstep from 0x%x with signal %d (%d)\n",
		reg(PROGCTR), s, p->signo);
	    fflush(stdout);
	}
	sigs_off();
	if (ptrace(PT_STEP, p->pid, reg(PROGCTR), p->signo) < 0) {
	    panic("error %d trying to step process", errno);
	}
	pwait(p->pid, &status);
	sigs_on();
	getinfo(p, status);
	adjustpc(p, PT_STEP);
	if (p->status == STOPPED and traceexec and not istraced(p)) {
	    printf("!! pstep ignored signal %d at 0x%x\n",
		p->signo, reg(PROGCTR));
	    fflush(stdout);
	}
	s = p->signo;
    } while (p->status == STOPPED and not istraced(p));
    if (traceexec) {
	printf("!! pstep to 0x%x on signal %d\n",
	    reg(PROGCTR), p->signo);
	fflush(stdout);
    }
    if (p->status != STOPPED) {
	if (p->exitval == 0) {
	    error("program exited\n");
	} else {
	    error("program exited with code %d\n", p->exitval);
	}
    }
}

/*
 * Return from execution when the given signal is pending.
 */

public psigtrace (p, sig, sw)
Process p;
int sig;
Boolean sw;
{
    if (sw) {
	p->sigset |= setrep(sig);
    } else {
	p->sigset &= ~setrep(sig);
    }
}

/*
 * Don't catch any signals.
 * Particularly useful when letting a process finish uninhibited.
 */

public unsetsigtraces (p)
Process p;
{
    p->sigset = 0;
}

/*
 * Turn off attention to signals not being caught.
 */

typedef int Intfunc();

private Intfunc *sigfunc[NSIG];

private sigs_off ()
{
    register int i;

    for (i = FIRSTSIG; i < LASTSIG; i++) {
	if (i != SIGKILL) {
	    sigfunc[i] = signal(i, SIG_IGN);
	}
    }
}

/*
 * Turn back on attention to signals.
 */

private sigs_on ()
{
    register int i;

    for (i = FIRSTSIG; i < LASTSIG; i++) {
	if (i != SIGKILL) {
	    signal(i, sigfunc[i]);
	}
    }
}

/*
 * Get process information from user area.
 */

public getinfo (p, status)
register Process p;
register int status;
{
    register int i;

    p->signo = (status&0177);
    p->exitval = ((status >> 8)&0377);
    if (p->signo != STOPPED) {
	p->status = FINISHED;
	p->pid = 0;
	setreg(PROGCTR, 0);
    } else {
	p->status = p->signo;
	p->signo = p->exitval;
	p->exitval = 0;
	p->sigcode = ptrace(PT_READ_U, p->pid, &uoffset->u_code, 0);
	for (i = 0; i <= regword(NREG + NFLREG - 1); i++) {
	    p->valid[i] = 0;
	    p->dirty[i] = 0;
	}
	savetty(stdout, &p->ttyinfo);
	p->sigstatus = ptrace(
	    PT_READ_U, p->pid, &uoffset->u_signal[p->signo], 0
	);
    }
}

/*
 * Set process's user area information from given process structure.
 * There are three things to do: determine which signal to give
 * the process upon restart, set any registers that have been changed
 * since execution stopped, and restore the terminal state to what
 * it was when execution stopped.
 */

public setinfo (p, signo)
register Process p;
int signo;
{
    register int i;
    register unsigned *w, mask;

    if (signo == DEFSIG) {
	if (istraced(p) and (p->sigstatus == 0 or p->sigstatus == 1)) {
	    p->signo = 0;
	}
    } else {
	p->signo = signo;
    }
    w = &p->dirty[0];
    mask = 1;
    for (i = 0; i < NREG; i++) {
	if ((*w & mask) != 0) {
	    writereg(p, i, p->reg[i]);
	}
	mask <<= 1;
	if (mask == 0) {
	    *w = 0;
	    ++w;
	    mask = 1;
	}
    }
    for (i = 0; i < NFLREG; i++) {
	if ((*w & mask) != 0) {
	    writeflreg(p, i, p->reg[NREG + i]);
	}
	mask <<= 1;
	if (mask == 0) {
	    *w = 0;
	    ++w;
	    mask = 1;
	}
    }
    restoretty(stdout, &(p->ttyinfo));
}

/*
 * Return the address associated with the current signal.
 * (Plus two since the address points to the beginning of a procedure).
 */

public Address usignal (p)
Process p;
{
    Address r;

    r = p->sigstatus;
    if (r != 0 and r != 1) {
	r += FUNCOFFSET;
    }
    return r;
}

/*
 * Get the value of a register.  Keep a copy around so that it doesn't
 * have to be retrieved from the process more than necessary.
 */

public Word reg (n)
int n;
{
    register Process p;

    p = process;
    extendedfloats = getfloatmask(p) & FLOAT_MC881;
    if ((p->valid[regword(n)] & regbit(n)) == 0) {
	if (n >= NREG) {
	    p->reg[n] = readflreg(p, n - NREG);
	} else {
	    p->reg[n] = readreg(p, n);
	}
	p->valid[regword(n)] |= regbit(n);
    }
    return p->reg[n];
}

public setreg (n, w)
int n;
Word w;
{
    register Process p = process;
    register int i, b;

    extendedfloats = getfloatmask(p);
    p->reg[n] = w;
    i = regword(n);
    b = regbit(n);
    p->valid[i] |= b;
    p->dirty[i] |= b;
}

/*
 * Structure for reading and writing by words, but dealing with bytes.
 */

typedef union {
    Word pword;
    Byte pbyte[sizeof(Word)];
} Pword;

/*
 * Read (write) from (to) the process' address space.
 * We must deal with ptrace's inability to look anywhere other
 * than at a word boundary.
 */

private Word fetch();
private store();

public pio (p, op, seg, buff, addr, nbytes)
Process p;
PioOp op;
PioSeg seg;
char *buff;
Address addr;
int nbytes;
{
    register int i;
    register Address newaddr;
    register char *cp;
    char *bufend;
    Pword w;
    Address wordaddr;
    int byteoff;

    if (p->status != STOPPED) {
	error("program is not active");
    }
    cp = buff;
    newaddr = addr;
    wordaddr = (newaddr&WMASK);
    if (wordaddr != newaddr) {
	w.pword = fetch(p, seg, wordaddr);
	for (i = newaddr - wordaddr; i < sizeof(Word) and nbytes > 0; i++) {
	    if (op == PREAD) {
		*cp++ = w.pbyte[i];
	    } else {
		w.pbyte[i] = *cp++;
	    }
	    nbytes--;
	}
	if (op == PWRITE) {
	    store(p, seg, wordaddr, w.pword);
	}
	newaddr = wordaddr + sizeof(Word);
    }
    byteoff = (nbytes&(~WMASK));
    nbytes -= byteoff;
    bufend = cp + nbytes;
    while (cp < bufend) {
	if (op == PREAD) {
	    w.pword = fetch(p, seg, newaddr);
	    for (i = 0; i < sizeof(Word); i++) {
		*cp++ = w.pbyte[i];
	    }
	} else {
	    for (i = 0; i < sizeof(Word); i++) {
		w.pbyte[i] = *cp++;
	    }
	    store(p, seg, newaddr, w.pword);
	}
	newaddr += sizeof(Word);
    }
    if (byteoff > 0) {
	w.pword = fetch(p, seg, newaddr);
	for (i = 0; i < byteoff; i++) {
	    if (op == PREAD) {
		*cp++ = w.pbyte[i];
	    } else {
		w.pbyte[i] = *cp++;
	    }
	}
	if (op == PWRITE) {
	    store(p, seg, newaddr, w.pword);
	}
    }
}

/*
 * Get a word from a process at the given address.
 * The address is assumed to be on a word boundary.
 *
 * A simple cache scheme is used to avoid redundant ptrace calls
 * to the instruction space since it is assumed to be pure.
 *
 * It is necessary to use a write-through scheme so that
 * breakpoints right next to each other don't interfere.
 */

private int nfetchs = 0, nreads = 0, nwrites = 0;

private Word fetch (p, seg, addr)
Process p;
PioSeg seg;
register int addr;
{
    register CacheWord *wp;
    register Word w;

    switch (seg) {
	case TEXTSEG:
	    ++nfetchs;
	    wp = &p->word[cachehash(addr)];
	    if (addr == 0 or wp->addr != addr) {
		++nreads;
		w = ptrace(PT_READ_I, p->pid, addr, 0);
		wp->addr = addr;
		wp->val = w;
	    } else {
		w = wp->val;
	    }
	    break;

	case DATASEG:
	    w = ptrace(PT_READ_D, p->pid, addr, 0);
	    break;

	default:
	    panic("fetch: bad seg %d", seg);
	    /* NOTREACHED */
    }
    return w;
}

/*
 * Put a word into the process' address space at the given address.
 * The address is assumed to be on a word boundary.
 */

private store (p, seg, addr, data)
Process p;
PioSeg seg;
int addr;
Word data;
{
    register CacheWord *wp;

    switch (seg) {
	case TEXTSEG:
	    ++nwrites;
	    wp = &p->word[cachehash(addr)];
	    wp->addr = addr;
	    wp->val = data;
	    ptrace(PT_WRITE_I, p->pid, addr, data);
	    break;

	case DATASEG:
	    ptrace(PT_WRITE_D, p->pid, addr, data);
	    break;

	default:
	    panic("store: bad seg %d", seg);
	    /* NOTREACHED */
    }
}

/*
 * Flush the instruction cache associated with a process.
 */

private cacheflush (p)
Process p;
{
    bzero(p->word, sizeof(p->word));
}

public printptraceinfo ()
{
    printf("%d fetchs, %d reads, %d writes\n", nfetchs, nreads, nwrites);
}

/*
 * Redirect input.
 * Assuming this is called from a child, we should be careful to avoid
 * (possibly) shared standard I/O buffers.
 */

private infrom (filename)
String filename;
{
    Fileid in;

    in = open(filename, 0);
    if (in == -1) {
	write(2, "can't read ", 11);
	write(2, filename, strlen(filename));
	write(2, "\n", 1);
	_exit(1);
    }
    fswap(0, in);
}

/*
 * Redirect standard output.
 * Same assumptions as for "infrom" above.
 */

private outto (filename)
String filename;
{
    Fileid out;

    out = creat(filename, 0666);
    if (out == -1) {
	write(2, "can't write ", 12);
	write(2, filename, strlen(filename));
	write(2, "\n", 1);
	_exit(1);
    }
    fswap(1, out);
}

/*
 * Swap file numbers, useful for redirecting standard input or output.
 */

private fswap (oldfd, newfd)
Fileid oldfd;
Fileid newfd;
{
    if (oldfd != newfd) {
	close(oldfd);
	dup(newfd);
	close(newfd);
    }
}
