/* $Header: execute.c,v 12.1 89/10/12 17:37:01 brunner Locked $ */
/* $Source: /fish/dbx/RCS/execute.c,v $ */

#ifndef lint
static char *rcsid = "$Header: execute.c,v 12.1 89/10/12 17:37:01 brunner Locked $";
#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 "frame.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_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, 0);
	    unsetallbps();
	    s = DEFSIG;
	    if ( !isbperr() || !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.
 *
 * To avoid HANGING, just stop.
 */

private intr ()
{
	isstopped = true;
}

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

/*
 * Resume execution.
 */

public resume (signo, last_bp)
int signo;
Address last_bp;
{
    register Process p;

    p = process;
    pcont(p, signo);

    pc = reg(PROGCTR);

    if (p->status != STOPPED)
    {
	/*
	 * Must unset all the breakpoints here since we won't be going 
	 * back to continue from here. First we set status to STOPPED 
	 * so that we can unset all the breakpoints, once we return we
	 * set it back so that we represent our true condition.
	 */
        p->status = STOPPED;
	if (last_bp > 0)
	    unsetbp(last_bp);
	else
	    unsetallbps();
        p->status = FINISHED;

	if (p->signo != 0)
	    error("program terminated by signal %d", p->signo);
	else if ( !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;
}

rtnfunc(f)
/*
  This routine returns from the current function if f == NULL
  or to the function named by f, provided it is on the
  stack.  If there is source info, it steps ahead to stop
  at the next source line in the same manner as dostep.
  Note:  in routines with no source, this code will give
  incorrect results in the prolog and in the epilog because
  the current frame hasn't been set up or the registers have
  be changed, respectively. Fixup for entry point done though.
*/
   Symbol f;
{
   Address of, nf, addr;
   int line;
   Symbol s, g;
   struct Frame frame;
   Frame frp = &frame;

   if (!isstopped) error("can't continue execution");

   isstopped = false;

   of = reg(1);
   nf = reg(1);

   if(!f)
   {
      /* if at entry point, do something special */
      if(isprolog(reg(PROGCTR)))
      {
	  of = reg(1);
	  addr = reg(15);
      }
      else 
      {
	  getcurfunc(frp,&g);
	  of = frp->reg[1];
	  addr = frp->reg[15];

          /* find the function to return to, if there */
          for(; g && g != program && isinline(g) ; )
          {
	     frp = nextfunc(frp,&g);
             of = frp->reg[1];
             addr = frp->reg[15];
          }   
       }
   }
   else 
   {
      /* if at entry point, do something special */
      if(isprolog(reg(PROGCTR))) 
      {
	 addr = reg(15);
	 of = reg(1);
	 g = whatblock(addr);
	 if(f == g) goto foundit;
	 else getcurfunc(frp,&g);
      }
      else
      {
	 /* get the current function info */
	 getcurfunc(frp,&g);
	 addr = frp->reg[15];
	 of = frp->reg[1];
      }

      /* find the function to return to, if there */
      for(; g && g != program && g != f; )
      {
         frp = nextfunc(frp,&g);
         of = frp->reg[1];
         addr = frp->reg[15];
      }   
   }

 /* C doesn't have a break from an if so we need a goto to here */
 foundit:

   if(f && f != g || !of)
   {
      error("can't return to function");
      isstopped = true;
      return;
   }

   /* crank until we get to the right stack level */
   do 
   {
      /* continue to that address */
      xto(addr,true);

      pc = reg(PROGCTR);
      nf = reg(1);
   }
   while ( nf < of && nf != 0 );

   /* did we stop at a line with line info ? */
   line = linelookup(addr);

   /* crank ahead until we find an addr with a line or no source info */
   while (line == 0 && (s = whatblock(addr)) && (isinline(s) || !nosource(s))) 
   {
      addr = nextaddr(addr, false);
      line = linelookup(addr);
      xto(addr,false);
   }

   isstopped = true;

   if(s = whatblock(addr)) useInstLoc = (Boolean) nosource(s);
   printnews();
}

/*
 * 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);
}

public 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, addr);
	unsetbp(addr);
	if (catchbps) {
	    unsetallbps();
	}
	if ( !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, 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, 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, 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, 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');
    }
}
