* Copyright (c) 1983 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
static char sccsid
[] = "@(#)process.c 5.3 (Berkeley) %G%";
static char rcsid
[] = "$Header: process.c,v 1.4 88/01/10 00:49:31 donn Exp $";
* This module contains the routines to manage the execution and
* tracing of the debuggee process.
typedef struct Process
*Process
;
* A cache of the instruction segment is kept to reduce the number
* of system calls. Might be better just to read the entire
* code space into memory.
* This structure holds the information we need from the user structure.
int pid
; /* process being traced */
int mask
; /* process status word */
Word reg
[NREG
]; /* process' registers */
Word oreg
[NREG
]; /* registers when process last stopped */
short status
; /* either STOPPED or FINISHED */
short signo
; /* signal that stopped process */
short sigcode
; /* extra signal information */
int exitval
; /* return value from exit() */
long sigset
; /* bit array of traced signals */
CacheWord word
[CACHESIZE
]; /* text segment cache */
Ttyinfo ttyinfo
; /* process' terminal characteristics */
Address sigstatus
; /* process' handler for current signal */
* These definitions are for the arguments to "pio".
typedef enum { PREAD
, PWRITE
} PioOp
;
typedef enum { TEXTSEG
, DATASEG
} PioSeg
;
private struct Process pbuf
;
#define MAXNCMDARGS 1000 /* maximum number of arguments to RUN */
private Boolean just_started
;
private String argv
[MAXNCMDARGS
];
private String infile
, outfile
;
* Initialize process information.
process
->status
= (coredump
) ? STOPPED
: NOTSTARTED
;
for (i
= 0; i
< NREG
; i
++) {
defregname(identname(buf
, false), i
);
defregname(identname("$ap", true), ARGP
);
for (i
= 0; i
< 8; i
++) {
defregname(identname(buf
, false), i
);
defregname(identname(buf
, false), i
+ 8);
defregname(identname("$fp", true), FRP
);
defregname(identname("$sp", true), STKP
);
defregname(identname("$pc", true), PROGCTR
);
coredump_readin(process
->mask
, process
->reg
, process
->signo
);
pc
= process
->reg
[PROGCTR
];
* Routines to get at process information from outside this module.
* 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
)
pstart(process
, argv
, infile
, outfile
);
reinit(argv
, infile
, outfile
);
if (process
->status
== STOPPED
) {
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 Boolean
remade(filename
)
b
= (Boolean
) (modtime
!= 0 and modtime
< s
.st_mtime
);
* Set up what signals we want to trace.
for (i
= 1; i
<= NSIG
; i
++) {
psigtrace(p
, SIGHUP
, false);
psigtrace(p
, SIGKILL
, false);
psigtrace(p
, SIGALRM
, false);
psigtrace(p
, SIGTSTP
, false);
psigtrace(p
, SIGCONT
, false);
psigtrace(p
, SIGCHLD
, false);
psigtrace(p
, SIGWINCH
, false);
* Initialize the argument list.
* Add an argument to the list for the debuggee.
if (argc
>= MAXNCMDARGS
) {
error("too many arguments");
* Set the standard input for the debuggee.
error("multiple input redirects");
* Set the standard output for the debuggee.
* Probably should check to avoid overwriting an existing file.
error("multiple output redirect");
* Start debuggee executing.
process
->status
= STOPPED
;
start(argv
, infile
, outfile
);
* 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.
dbintr
= signal(SIGINT
, intr
);
error("can't continue execution");
if (not isbperr() or not bpact()) {
* 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.
pc
= process
->reg
[PROGCTR
];
if (p
->status
!= STOPPED
) {
error("program terminated by signal %d", p
->signo
);
} else if (not runfirst
) {
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.
error("can't continue execution");
error("can't continue execution");
} while (newfrp
< oldfrp
and newfrp
!= 0);
* Continue execution until the current function returns, or,
* if the given argument is non-nil, until execution returns to
* somewhere within the given function.
error("can't continue execution");
} else if (f
!= nil
and not isactive(f
)) {
error("%s is not active", symname(f
));
error("no place to return to");
if (t
== f
or addr
== nil
) break;
* 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
printf("!! stepping over 0x%x\n", process
->reg
[PROGCTR
]);
printf("!! stepped over to 0x%x\n", process
->reg
[PROGCTR
]);
* Resume execution up to the given address. We can either ignore
* breakpoints (stepto) or catch them (contto).
private xto (addr
, catchbps
)
curpc
= process
->reg
[PROGCTR
];
printf("!! stepping from 0x%x to 0x%x\n", curpc
, addr
);
* Print the status of the process.
* This routine does not return.
if (process
->status
== FINISHED
) {
fprintf(stderr
, "\nEntering debugger ...\n");
setcurfunc(whatblock(pc
));
if (process
->signo
== SIGINT
) {
} else if (isbperr() and isstopped
) {
printlines(curline
, curline
);
* Print out the current location in the debuggee.
printname(stdout
, curfunc
);
if (curline
> 0 and not useInstLoc
) {
* Some functions for testing the state of the process.
public Boolean
notstarted(p
)
return (Boolean
) (p
->status
== NOTSTARTED
);
public Boolean
isfinished(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 SIGTRAP handler.
if (p
->status
== STOPPED
and p
->signo
== SIGTRAP
) {
* Return the signal number that stopped the process.
public integer
errnum (p
)
* Return the signal code associated with the signal.
public integer
errcode (p
)
* Return the termination code of the process.
public integer
exitcode (p
)
* These routines are used to access the debuggee process from
* 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 read_err(), write_err();
* Read from the process' instruction area.
public iread(buff
, addr
, nbytes
)
f
= onsyserr(EIO
, read_err
);
coredump_readtext(buff
, addr
, nbytes
);
pio(process
, PREAD
, TEXTSEG
, buff
, addr
, nbytes
);
* Write to the process' instruction area, usually in order to set
public iwrite(buff
, addr
, nbytes
)
error("no process to write to");
f
= onsyserr(EIO
, write_err
);
pio(process
, PWRITE
, TEXTSEG
, buff
, addr
, nbytes
);
* Read for the process' data area.
public dread(buff
, addr
, nbytes
)
f
= onsyserr(EFAULT
, read_err
);
coredump_readdata(buff
, addr
, nbytes
);
f
= onsyserr(EIO
, read_err
);
pio(process
, PREAD
, DATASEG
, buff
, addr
, nbytes
);
* Write to the process' data area.
public dwrite(buff
, addr
, nbytes
)
error("no process to write to");
f
= onsyserr(EIO
, write_err
);
pio(process
, PWRITE
, DATASEG
, buff
, addr
, nbytes
);
* Trap for errors in reading or writing to a process.
* The current approach is to "ignore" read errors and complain
* bitterly about write errors.
error("can't write to process (address 0x%x)", badaddr
);
#define WMASK (~(sizeof(Word) - 1))
#define cachehash(addr) ((unsigned) ((addr >> 2) % CACHESIZE))
#define ischild(pid) ((pid) == 0)
#define traceme() ptrace(0, 0, 0, 0)
#define setrep(n) (1 << ((n)-1))
#define istraced(p) (p->sigset&setrep(p->signo))
* Ptrace options (specified in first argument).
#define UREAD 3 /* read from process's user structure */
#define UWRITE 6 /* write to process's user structure */
#define IREAD 1 /* read from process's instruction space */
#define IWRITE 4 /* write to process's instruction space */
#define DREAD 2 /* read from process's data space */
#define DWRITE 5 /* write to process's data space */
#define CONT 7 /* continue stopped process */
#define SSTEP 9 /* continue for approximately one instruction */
#define PKILL 8 /* terminate the process */
# define readreg(p, r) ptrace(10, p->pid, r, 0)
# define writereg(p, r, v) ptrace(11, p->pid, r, v)
# define readreg(p, r) ptrace(UREAD, p->pid, regloc(r), 0);
# define writereg(p, r, v) ptrace(UWRITE, p->pid, regloc(r), v);
* 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.
private pstart(p
, argv
, infile
, outfile
)
psigtrace(p
, SIGTRAP
, true);
if (p
->status
!= STOPPED
) {
fprintf(stderr
, "warning: cannot execute %s\n", argv
[0]);
* Terminate a ptrace'd process.
if (p
!= nil
and p
->pid
!= 0) {
ptrace(PKILL
, p
->pid
, 0, 0);
* 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.
error("program is not active");
printf("!! pcont from 0x%x with signal %d (%d)\n",
p
->reg
[PROGCTR
], s
, p
->signo
);
if (ptrace(CONT
, p
->pid
, p
->reg
[PROGCTR
], p
->signo
) < 0) {
panic("error %d trying to continue process", errno
);
if (p
->status
== STOPPED
and traceexec
and not istraced(p
)) {
printf("!! ignored signal %d at 0x%x\n",
p
->signo
, p
->reg
[PROGCTR
]);
} while (p
->status
== STOPPED
and not istraced(p
));
printf("!! pcont to 0x%x on signal %d\n", p
->reg
[PROGCTR
], p
->signo
);
* Single step as best ptrace can.
printf("!! pstep from 0x%x with signal %d (%d)\n",
p
->reg
[PROGCTR
], s
, p
->signo
);
if (ptrace(SSTEP
, p
->pid
, p
->reg
[PROGCTR
], p
->signo
) < 0) {
panic("error %d trying to step process", errno
);
if (p
->status
== STOPPED
and p
->signo
== SIGTRAP
) {
if (p
->status
== STOPPED
and traceexec
and not istraced(p
)) {
printf("!! pstep ignored signal %d at 0x%x\n",
p
->signo
, p
->reg
[PROGCTR
]);
} while (p
->status
== STOPPED
and not istraced(p
));
printf("!! pstep to 0x%x on signal %d\n",
p
->reg
[PROGCTR
], p
->signo
);
if (p
->status
!= STOPPED
) {
error("program exited\n");
error("program exited with code %d\n", p
->exitval
);
* Return from execution when the given signal is pending.
public psigtrace(p
, sig
, sw
)
p
->sigset
|= setrep(sig
);
p
->sigset
&= ~setrep(sig
);
* Don't catch any signals.
* Particularly useful when letting a process finish uninhibited.
* Turn off attention to signals not being caught.
private Intfunc
*sigfunc
[NSIG
];
for (i
= FIRSTSIG
; i
< LASTSIG
; i
++) {
sigfunc
[i
] = signal(i
, SIG_IGN
);
* Turn back on attention to signals.
for (i
= FIRSTSIG
; i
< LASTSIG
; i
++) {
* Get process information from user area.
private getinfo (p
, status
)
p
->signo
= (status
&0177);
p
->exitval
= ((status
>> 8)&0377);
if (p
->signo
!= STOPPED
) {
p
->mask
= readreg(p
, RPS
);
p
->sigcode
= ptrace(UREAD
, p
->pid
, &((struct user
*)0)->u_code
, 0);
p
->mask
= readreg(p
, PS
);
for (i
= 0; i
< NREG
; i
++) {
p
->reg
[i
] = readreg(p
, rloc
[i
]);
if (p
->status
== STOPPED
and p
->signo
== SIGTRAP
and
p
->reg
[PROGCTR
] > CODESTART
savetty(stdout
, &(p
->ttyinfo
));
addr
= (Address
) &(((struct user
*) 0)->u_signal
[p
->signo
]);
p
->sigstatus
= (Address
) ptrace(UREAD
, p
->pid
, addr
, 0);
* Set process's user area information from given process structure.
private setinfo (p
, signo
)
if (istraced(p
) and (p
->sigstatus
== 0 or p
->sigstatus
== 1)) {
for (i
= 0; i
< NREG
; i
++) {
if ((r
= p
->reg
[i
]) != p
->oreg
[i
]) {
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
)
* Structure for reading and writing by words, but dealing with bytes.
Byte pbyte
[sizeof(Word
)];
* 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 pio(p
, op
, seg
, buff
, addr
, nbytes
)
register Address newaddr
;
if (p
->status
!= STOPPED
) {
error("program is not active");
wordaddr
= (newaddr
&WMASK
);
if (wordaddr
!= newaddr
) {
w
.pword
= fetch(p
, seg
, wordaddr
);
for (i
= newaddr
- wordaddr
; i
< sizeof(Word
) and nbytes
> 0; i
++) {
store(p
, seg
, wordaddr
, w
.pword
);
newaddr
= wordaddr
+ sizeof(Word
);
byteoff
= (nbytes
&(~WMASK
));
* Must copy a byte at a time, buffer not word addressable.
w
.pword
= fetch(p
, seg
, newaddr
);
for (i
= 0; i
< sizeof(Word
); i
++)
for (i
= 0; i
< sizeof(Word
); i
++)
store(p
, seg
, newaddr
, w
.pword
);
* Buffer, word aligned, act normally...
*((Word
*) cp
) = fetch(p
, seg
, newaddr
);
store(p
, seg
, newaddr
, *((Word
*) cp
));
w
.pword
= fetch(p
, seg
, newaddr
);
for (i
= 0; i
< byteoff
; i
++) {
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 Integer nfetchs
, nreads
, nwrites
;
private Word
fetch(p
, seg
, addr
)
wp
= &p
->word
[cachehash(addr
)];
if (addr
== 0 or wp
->addr
!= addr
) {
w
= ptrace(IREAD
, p
->pid
, addr
, 0);
w
= ptrace(DREAD
, p
->pid
, addr
, 0);
panic("fetch: bad seg %d", seg
);
* 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
)
wp
= &p
->word
[cachehash(addr
)];
ptrace(IWRITE
, p
->pid
, addr
, data
);
ptrace(DWRITE
, p
->pid
, addr
, data
);
panic("store: bad seg %d", seg
);
* Flush the instruction cache associated with a process.
bzero(p
->word
, sizeof(p
->word
));
printf("%d fetchs, %d reads, %d writes\n", nfetchs
, nreads
, nwrites
);
* Assuming this is called from a child, we should be careful to avoid
* (possibly) shared standard I/O buffers.
private infrom (filename
)
write(2, "can't read ", 11);
write(2, filename
, strlen(filename
));
* Redirect standard output.
* Same assumptions as for "infrom" above.
out
= creat(filename
, 0666);
write(2, "can't write ", 12);
write(2, filename
, strlen(filename
));
* Swap file numbers, useful for redirecting standard input or output.
private fswap(oldfd
, newfd
)
* Signal name manipulation.
private String signames
[NSIG
] = {
"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",
"VTALRM", "PROF", "WINCH", "USR1", "USR2"
* Get the signal number associated with a given name.
* The name is first translated to upper case if necessary.
public integer
siglookup (s
)
if (*p
>= 'a' and *p
<= 'z') {
if (buf
[0] == 'S' and buf
[1] == 'I' and buf
[2] == 'G') {
if (i
>= sizeof(signames
) div
sizeof(signames
[0])) {
error("signal \"%s\" unknown", s
);
if (signames
[i
] != nil
and streq(signames
[i
], p
)) {
* Print all signals being ignored by the debugger.
* These signals are auotmatically
* passed on to the debugged process.
public printsigsignored (p
)
* Print all signals being intercepted by
* the debugger for the specified process.
public printsigscaught(p
)
for (s
= 1; s
< sizeof(signames
) div
sizeof(signames
[0]); s
++) {
if (signames
[s
] != nil
) {
printf("%s%s", separator
, signames
[s
]);
if (separator
[0] == ' ') {