/* Copyright (c) 1982 Regents of the University of California */
static char sccsid
[] = "@(#)process.c 1.13 (Berkeley) %G%";
* 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.
#define CSIZE 1003 /* size of instruction cache */
* 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 */
int exitval
; /* return value from exit() */
long sigset
; /* bit array of traced signals */
CacheWord word
[CSIZE
]; /* 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 100 /* 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
);
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
)
reinit(argv
, infile
, outfile
);
pstart(process
, 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);
* 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");
* This routine is called if we get an interrupt while "running" px
* 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 unexpectedly exited with %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. It is assumed that
* no breakpoints exist between the current address and the one
* we're stepping to. This saves us from setting all the breakpoints.
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
) {
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
);
* Return the signal number which stopped the process.
* 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(EIO
, read_err
);
coredump_readdata(buff
, addr
, nbytes
);
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
);
* This magic macro enables us to look at the process' registers
#define regloc(reg) (ctob(UPAGES) + ( sizeof(int) * (reg) ))
#define WMASK (~(sizeof(Word) - 1))
#define cachehash(addr) ((unsigned) ((addr >> 2) % CSIZE))
#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 */
* 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);
write(2, "can't exec ", 11);
write(2, argv
[0], strlen(argv
[0]));
if (p
->status
!= STOPPED
) {
error("program could not begin execution");
* 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 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 (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 pc 0x%x with signal %d (%d)\n",
p
->reg
[PROGCTR
], signo
, p
->signo
);
if (ptrace(SSTEP
, p
->pid
, p
->reg
[PROGCTR
], p
->signo
) < 0) {
panic("error %d trying to step process", errno
);
printf("!! pstep to pc 0x%x on signal %d\n", p
->reg
[PROGCTR
], p
->signo
);
if (p
->status
!= STOPPED
) {
error("program unexpectedly exited with %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.
R0
, R1
, R2
, R3
, R4
, R5
, R6
, R7
, R8
, R9
, R10
, R11
, AP
, FP
, SP
, PC
private getinfo(p
, status
)
p
->signo
= (status
&0177);
p
->exitval
= ((status
>> 8)&0377);
if (p
->signo
!= STOPPED
) {
p
->mask
= ptrace(UREAD
, p
->pid
, regloc(PS
), 0);
for (i
= 0; i
< NREG
; i
++) {
p
->reg
[i
] = ptrace(UREAD
, p
->pid
, regloc(rloc
[i
]), 0);
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
]) {
ptrace(UWRITE
, p
->pid
, regloc(rloc
[i
]), r
);
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
));
*((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
);
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
)