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

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

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

/*
 * Execution management.
 */

#include "defs.h"
#include "execute.h"
#include "process.h"
#include "procinfo.h"
#include "machine.h"
#include "events.h"
#include "tree.h"
#include "eval.h"
#include "operators.h"
#include "source.h"
#include "object.h"
#include "mappings.h"
#include "coredump.h"
#include "runtime.h"
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifndef public

#define DEFSIG -1

#endif

#define MAXNCMDARGS 1000         /* maximum number of arguments to RUN */

private Boolean just_started;
private int argc;
private String argv[MAXNCMDARGS];
private String infile, outfile;

/*
 * Begin execution.
 *
 * We set a breakpoint at the end of the code so that the
 * process data doesn't disappear after the program terminates.
 */

private Boolean remade();

public start (argv, infile, outfile)
String argv[];
String infile, outfile;
{
    String pargv[4];
    Node cond;

    if (coredump) {
	coredump = false;
	fclose(corefile);
	coredump_close();
    }
    if (argv == nil) {
	argv = pargv;
	pargv[0] = objname;
	pargv[1] = nil;
    } else {
	argv[argc] = nil;
    }
    pstart(process, argv, infile, outfile);
    if (remade(objname)) {
	reinit(argv, infile, outfile);
    }
    if (process->status == STOPPED) {
	pc = CODESTART;
	setcurfunc(program);
	if (objsize != 0) {
	    cond = build(O_EQ, build(O_SYM, pcsym), build(O_LCON, lastaddr()));
	    event_once(cond, buildcmdlist(build(O_ENDX)));
	}
    }
}

/*
 * Check to see if the object file has changed since the symbolic
 * information last was read.
 */

private time_t modtime;

private Boolean remade (filename)
String filename;
{
    struct stat s;
    Boolean b;

    stat(filename, &s);
    b = (Boolean) (modtime != 0 and modtime < s.st_mtime);
    modtime = s.st_mtime;
    return b;
}

/*
 * Initialize the argument list.
 */

public arginit ()
{
    infile = nil;
    outfile = nil;
    argv[0] = objname;
    argc = 1;
}

/*
 * Add an argument to the list for the debuggee.
 */

public newarg (arg)
String arg;
{
    if (argc >= MAXNCMDARGS) {
	error("too many arguments");
    }
    argv[argc++] = arg;
}

/*
 * Set the standard input for the debuggee.
 */

public inarg (filename)
String filename;
{
    if (infile != nil) {
	error("multiple input redirects");
    }
    infile = filename;
}

/*
 * Set the standard output for the debuggee.
 * Probably should check to avoid overwriting an existing file.
 */

public outarg (filename)
String filename;
{
    if (outfile != nil) {
	error("multiple output redirect");
    }
    outfile = filename;
}

/*
 * Start debuggee executing.
 */

public run ()
{
    process->status = STOPPED;
    fixbps();
    curline = 0;
    start(argv, infile, outfile);
    just_started = true;
    isstopped = false;
    cont(0);
}

/*
 * Continue execution wherever we left off.
 *
 * Note that this routine never returns.  Eventually bpact() will fail
 * and we'll call printstatus or step will call it.
 */

typedef int Intfunc();

private Intfunc *dbintr;
private intr();

public cont (signo)
int signo;
{
    int s;

    dbintr = signal(SIGINT, intr);
    if (just_started) {
	just_started = false;
    } else {
	if (not isstopped) {
	    error("can't continue execution");
	}
	isstopped = false;
	stepover();
    }
    s = signo;
    for (;;) {
	if (single_stepping) {
	    printnews();
	} else {
	    setallbps();
	    resume(s);
	    unsetallbps();
	    s = DEFSIG;
	    if (not isbperr() or not bpact()) {
		printstatus();
	    }
	}
	stepover();
    }
    /* NOTREACHED */
}

/*
 * This routine is called if we get an interrupt while "running"
 * but actually in the debugger.  Could happen, for example, while
 * processing breakpoints.
 *
 * We basically just want to keep going; the assumption is
 * that when the process resumes it will get the interrupt,
 * which will then be handled.
 */

private intr ()
{
    signal(SIGINT, intr);
}

public fixintr ()
{
    signal(SIGINT, dbintr);
}

/*
 * Resume execution.
 */

public resume (signo)
int signo;
{
    register Process p;

    p = process;
    pcont(p, signo);
    pc = reg(PROGCTR);
    if (p->status != STOPPED) {
	if (p->signo != 0) {
	    error("program terminated by signal %d", p->signo);
	} else if (not runfirst) {
	    if (p->exitval == 0) {
		error("program exited");
	    } else {
		error("program exited with code %d", p->exitval);
	    }
	}
    }
}

/*
 * Continue execution up to the next source line.
 *
 * There are two ways to define the next source line depending on what
 * is desired when a procedure or function call is encountered.  Step
 * stops at the beginning of the procedure or call; next skips over it.
 */

/*
 * Stepc is what is called when the step command is given.
 * It has to play with the "isstopped" information.
 */

public stepc ()
{
    if (not isstopped) {
	error("can't continue execution");
    }
    isstopped = false;
    dostep(false);
    isstopped = true;
}

public next ()
{
    if (not isstopped) {
	error("can't continue execution");
    }
    isstopped = false;
    donext();
    isstopped = true;
}

/*
 * Continue execution until the current function returns, or,
 * if the given argument is non-nil, until execution returns to
 * somewhere within the given function.
 */

public rtnfunc (f)
Symbol f;
{
    Address addr;
    Symbol t;

    if (not isstopped) {
	error("can't continue execution");
    } else if (f != nil and not isactive(f)) {
	error("%s is not active", symname(f));
    } else {
	addr = return_addr();
	if (addr == nil) {
	    error("no place to return to");
	} else {
	    isstopped = false;
	    contto(addr);
	    if (f != nil) {
		for (;;) {
		    t = whatblock(pc);
		    addr = return_addr();
		if (t == f or addr == nil) break;
		    contto(addr);
		}
	    }
	    if (not bpact()) {
		isstopped = true;
		printstatus();
	    }
	}
    }
}

/*
 * Single-step over the current machine instruction.
 *
 * If we're single-stepping by source line we want to step to the
 * next source line.  Otherwise we're going to continue so there's
 * no reason to do all the work necessary to single-step to the next
 * source line.
 */

public stepover ()
{
    Boolean b;

    if (traceexec) {
	printf("!! stepping over 0x%x\n", reg(PROGCTR));
    }
    if (single_stepping) {
	dostep(false);
    } else {
	b = inst_tracing;
	inst_tracing = true;
	dostep(false);
	inst_tracing = b;
    }
    if (traceexec) {
	printf("!! stepped over to 0x%x\n", reg(PROGCTR));
    }
}

/*
 * Resume execution up to the given address.  We can either ignore
 * breakpoints (stepto) or catch them (contto).
 */

public stepto (addr)
Address addr;
{
    xto(addr, false);
}

private contto (addr)
Address addr;
{
    xto(addr, true);
}

private xto (addr, catchbps)
Address addr;
boolean catchbps;
{
    Address curpc;

    if (catchbps) {
	stepover();
    }
    curpc = reg(PROGCTR);
    if (addr != curpc) {
	if (traceexec) {
	    printf("!! stepping from 0x%x to 0x%x\n", curpc, addr);
	}
	if (catchbps) {
	    setallbps();
	}
	setbp(addr);
	resume(DEFSIG);
	unsetbp(addr);
	if (catchbps) {
	    unsetallbps();
	}
	if (not isbperr()) {
	    printstatus();
	}
    }
}

/*
 * Print the status of the process.
 * This routine does not return.
 */

public printstatus ()
{
    if (process->status == FINISHED) {
	exit(0);
    } else {
	if (runfirst) {
	    fprintf(stderr, "\nEntering debugger ...\n");
	    printheading();
	    init();
	}
	setcurfunc(whatblock(pc));
	getsrcpos();
	if (process->signo == SIGINT) {
	    isstopped = true;
	    printerror();
	} else if (isbperr() and isstopped) {
	    printf("stopped ");
	    printloc();
	    putchar('\n');
	    if (curline > 0) {
		printlines(nil, curline, curline);
	    } else {
		printinst(pc, pc);
	    }
	    erecover();
	} else {
	    fixintr();
	    isstopped = true;
	    printerror();
	}
    }
}

/*
 * Print out the current location in the debuggee.
 */

public printloc ()
{
    printf("in ");
    printname(stdout, curfunc);
    putchar(' ');
    if (curline > 0 and not useInstLoc) {
	printsrcpos();
    } else {
	useInstLoc = false;
	curline = 0;
	printf("at 0x%x", pc);
    }
}

/*
 * Some functions for testing the state of the process.
 */

public Boolean notstarted (p)
Process p;
{
    return (Boolean) (p->status == NOTSTARTED);
}

public Boolean isfinished (p)
Process p;
{
    return (Boolean) (p->status == FINISHED);
}

/*
 * Predicate to test if the reason the process stopped was because
 * of a breakpoint.  If so, as a side effect clear the local copy of
 * signal handler associated with process.  We must do this so as to
 * not confuse future stepping or continuing by possibly concluding
 * the process should continue with a BP_ERRNO handler.
 */

public boolean isbperr ()
{
    Process p;
    boolean b;

    p = process;
    if (p->status == STOPPED and p->signo == BP_ERRNO) {
	b = true;
	p->sigstatus = 0;
    } else {
	b = false;
    }
    return b;
}

/*
 * Return the signal number that stopped the process.
 */

public int errnum (p)
Process p;
{
    return p->signo;
}

/*
 * Return the signal code associated with the signal.
 */

public int errcode (p)
Process p;
{
    return p->sigcode;
}

/*
 * Return the termination code of the process.
 */

public int exitcode (p)
Process p;
{
    return p->exitval;
}

/*
 * These routines are used to access the debuggee process from
 * outside this module.
 *
 * They invoke "pio" which eventually leads to a call to "ptrace".
 * The system generates an I/O error when a ptrace fails.  During reads
 * these are ignored, during writes they are reported as an error, and
 * for anything else they cause a fatal error.
 */

extern Intfunc *onsyserr();

private badaddr;
private read_err(), write_err();

/*
 * Read from the process' instruction area.
 */

public iread (buff, addr, nbytes)
char *buff;
Address addr;
int nbytes;
{
    Intfunc *f;

    f = onsyserr(EIO, read_err);
    badaddr = addr;
    if (coredump) {
	coredump_readtext(buff, addr, nbytes);
    } else {
	pio(process, PREAD, TEXTSEG, buff, addr, nbytes);
    }
    onsyserr(EIO, f);
}

/* 
 * Write to the process' instruction area, usually in order to set
 * or unset a breakpoint.
 */

public iwrite (buff, addr, nbytes)
char *buff;
Address addr;
int nbytes;
{
    Intfunc *f;

    if (coredump) {
	error("no process to write to");
    }
    f = onsyserr(EIO, write_err);
    badaddr = addr;
    pio(process, PWRITE, TEXTSEG, buff, addr, nbytes);
    onsyserr(EIO, f);
}

/*
 * Read for the process' data area.
 */

public dread (buff, addr, nbytes)
char *buff;
Address addr;
int nbytes;
{
    Intfunc *f;

    badaddr = addr;
    if (coredump) {
	f = onsyserr(EFAULT, read_err);
	coredump_readdata(buff, addr, nbytes);
	onsyserr(EFAULT, f);
    } else {
	f = onsyserr(EIO, read_err);
	pio(process, PREAD, DATASEG, buff, addr, nbytes);
	onsyserr(EIO, f);
    }
}

/*
 * Write to the process' data area.
 */

public dwrite (buff, addr, nbytes)
char *buff;
Address addr;
int nbytes;
{
    Intfunc *f;

    if (coredump) {
	error("no process to write to");
    }
    f = onsyserr(EIO, write_err);
    badaddr = addr;
    pio(process, PWRITE, DATASEG, buff, addr, nbytes);
    onsyserr(EIO, f);
}

/*
 * Trap for errors in reading or writing to a process.
 * The current approach is to "ignore" read errors and complain
 * bitterly about write errors.
 */

private read_err ()
{
    /*
     * Ignore.
     */
}

private write_err ()
{
    error("can't write to process (address 0x%x)", badaddr);
}

/*
 * Signal name manipulation.
 */

private String signames[NSIG] = {
    0,
    "HUP", "INT", "QUIT", "ILL", "TRAP",
    "IOT", "EMT", "FPE", "KILL", "BUS",
    "SEGV", "SYS", "PIPE", "ALRM", "TERM",
    0, "STOP", "TSTP", "CONT", "CHLD",
    "TTIN", "TTOU", "TINT", "XCPU", "XFSZ",
};

/*
 * Get the signal number associated with a given name.
 * The name is first translated to upper case if necessary.
 */

public int siglookup (s)
String s;
{
    register char *p, *q;
    char buf[100];
    int i;

    p = s;
    q = buf;
    while (*p != '\0') {
	if (*p >= 'a' and *p <= 'z') {
	    *q = (*p - 'a') + 'A';
	} else {
	    *q = *p;
	}
	++p;
	++q;
    }
    *q = '\0';
    p = buf;
    if (buf[0] == 'S' and buf[1] == 'I' and buf[2] == 'G') {
	p += 3;
    }
    i = 1;
    for (;;) {
	if (i >= sizeof(signames) div sizeof(signames[0])) {
	    error("signal \"%s\" unknown", s);
	    i = 0;
	    break;
	}
	if (signames[i] != nil and streq(signames[i], p)) {
	    break;
	}
	++i;
    }
    return i;
}

/*
 * Print all signals being ignored by the debugger.
 * These signals are auotmatically
 * passed on to the debugged process.
 */

public printsigsignored (p)
Process p;
{
    printsigs(~p->sigset);
}

/*
 * Print all signals being intercepted by
 * the debugger for the specified process.
 */

public printsigscaught (p)
Process p;
{
    printsigs(p->sigset);
}

private printsigs (set)
int set;
{
    int s;
    char separator[2];

    separator[0] = '\0';
    for (s = 1; s < sizeof(signames) div sizeof(signames[0]); s++) {
	if (set & setrep(s)) {
	    if (signames[s] != nil) {
		printf("%s%s", separator, signames[s]);
		separator[0] = ' ';
		separator[1] = '\0';
	    }
	}
    }
    if (separator[0] == ' ') {
	putchar('\n');
    }
}
