home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C/C++ Interactive Guide
/
c-cplusplus-interactive-guide.iso
/
c_ref
/
csource4
/
262_01
/
debug.txt
< prev
next >
Wrap
Text File
|
1988-03-30
|
21KB
|
608 lines
.he /page #
.ce 2
Add a Source Debugger to Your C compiler
by Robert Ramey
I really needed a better way of debugging my C programs. I am
happy with my compiler but it doesn't include a source level
debugger. Assembler language debuggers are difficult to use
with compiled programs. After a little thought and some more work
I developed the source debugger you see in this article. It:
- is written entirely in C (except for 7 assembler language
statements). Thus it should be transportable to other environments.
- does not requiere that programs to be analyzed be recompiled.
They must be relinked however.
- can trace and display program operation including entry of
functions with display of arguments and return values.
- can set break points at any function entry.
- can display function arguments and global variables using any
convenient format. Can also display data structures pointed to
by arguments and global variables.
- can be used to analyze programs written in other languages
that use similar argument passing and calling conventions.
This debugger cannot display local variables or trace or set
break points which are not function entry points.
Also the debugger can trace only the first level of a
recurrsively called function.
To overcome these limitations would have made the project much
bigger and in many cases would have requiered fiddling with the
compiler. At least for now, I decided it wasn't worth it.
1. How to Use the Debugger
Use of the debugger will vary somewhat from system to system. The
description here applies to my computing environment.
This consists of a SB-180 single board computer,
386k floppy, and a Beehive terminal. I use CP/M compatible ZRDOS,
ZAS assembler, ZLINK linker and Q/C C compiler.
Suppose I want debug the program HELLO which prints a simple
message on the screen and terminates.
#include <stdio>
main()
{
printf("Hello World\n");
}
Normally I link this program with the command:
ZLINK HELLO,CRUNLIB/
and execute with the following:
HELLO
When I want to analyze and control execution with my debugger I
link with the command:
ZLINK DEBUG,HELLO,CRUNLIB/ $S
and execute with:
DEBUG
On encountering $S in the link command line, ZLINK generates a symbol
table named DEBUG.SYM for the resulting linked program. This
symbol table is key to the debugging process.
Before starting execution of the main() function,
the symbol file is read, the debugger
command interpreter takes control and prompts with *. Now I
can type in debugger commands.
2. Debugger Commands
Debugger commands are one letter optionally followed by one or
more arguments. The command letter can be upper or lower case.
2.1 Display a symbol
S [<symbol>] [<hex number>]
The S command is to set or display symbols. S without arguments
displays the whole symbol table. S with one argument displays
the current value of that symbol. The value of a symbol is usually
the address of a data item or function entry point.
If a hex number is specified, the symbol is assigned a new value.
If the symbol did not previously exist, it is created at this
time. Examples:
*S main
02C2 main
If the symbol has been designated as a break point, the B character
preceeds the value. Normally this command is used to check which
symbols exist and what their current state is. Symbols are loaded
from the DEBUG.SYM file at the beginning of program execution so
it is rare that new symbols need be defined.
2.2 Display contents of memory
D [<symbol>] [<format string>]
The D command is used to display contents of memory.
If D is used with a symbol argument, the contents of that memory
location are displayed. Optionally one may specify a format string to
be used to display the memory contents. (see below).
If D is used without arguments, the contents of memory for each
symbol in the data segment are displayed. Examples:
*D stdin
stdin=4548
*D stdin input file is %d
stdin=input file is 17736
2.3 Specify Format String for a Symbol
F <symbol> [<format string>]
The F command is used to specify the format string to used to display
the contents addressed by the symbol. This format string is used
by a printf() statement whenever the contents of this memory location
are displayed. One memory location will be displayed for
each % character in the format string. If no format string has been
associated with a symbol, a default format string of %x is assumed.
The S command can be used to display a symbols current format string.
Format strings are the same ones used by the printf() statement except
that the *, (, and ) characters have a special meaning. They are used
to permit the formated display of the objects of pointers.
The * character preceeding a % character indicates that the object to
be displayed is pointed to by the contents of memory.
As an example, take the following program fragment:
int a[] = {1, 2, 3,}, /* array */
*b, /* pointer to an integer */
*c; /* pointer to array */
...
*b = 0;
c[0] = 4; c[1] = 5; c[2] = 6;
appropriate debugger commands might be:
*D a a[0]=%d, a[1]=%d, a[2]=%d
a a[0]=1, a[1]=2, a[2]=3
*D b &b=%x
b &b=5F6A
*D b b=*%d
b 0
*D c *(c[0]=%d, c[1]=%d, c[2]=%d)
c c[0]=4, c[1]=5, c[2]=6
The ( character pushes the next address on to a stack and loads
the pointed to address. The ) character recovers the saved address.
Parenthesis can be used when the data structures to be displayed
are more complex. For example, we might store a series of words
as an array of linked lists.
struct word {
struct word *next, *prev;
char *spelling;
}
struct word *chain[ARRAYSIZE];
The command to display the first three pointers would be:
*D chain %x %x %x
chain 4556 4578 45A9
To display the first structure in each of the first two chains:
*D chain *(nxt=%x, prv=%x, %s), *(nxt=%, prv=%x, %s)
chain (nxt=5543, prv=554B, martha), (nxt=558B, prv=83BF, anne)
To display the first two structures in the first chain:
*D chain *(next=*(%x %x %s) prev=%x %s)
chain (next=(4384 340D george) prev=89E4 martha)
When a symbol corresponds to a function entry point, the format
string is used to display the arguments when
the function is entered and return value when the function exited.
The first % or pair of parenthesis is used as the format for the
return value while the rest of the format string is used for the
arguments. For example, if the program contains:
fp = fopen("LST:","r");
and the following F debugger command is specified:
*F fopen file pointer=%x filename=%s mode=%s
the following will be displayed as the program executes:
>fopen filename=LST: mode=r
...
<fopen=file pointer=457D
One final tricky example on the format string:
a[] = "abc";
b = "abc";
a and b cannot be displayed with same format string. The correct
format strings are:
*D a %c%c%c
a abc
*D b %s
b abc
This fooled me when I first started using the debugger.
Think about it.
The F, D, and B commands can be used to assign a format string to a
symbol. In all cases the most recently specified format string
becomes the default format string. The F command can be used
to reinitialize the format string for a symbol to null.
2.4 Set/Reset Trace Mode
T
Program flow is traced when trace mode is on. This means that
as each function is entered and exited its name, arguments
and return value are displayed. In order for arguments to be
displayed a format string must have been previously supplied
for the function.
2.5 Set/Reset break point
B [<symbol>] [<format string>]
The indicated symbol becomes a break point.
This means that when the function corresponding to the symbol
is entered, normal execution will be suspended, the
name of the function with its arguments will be displayed,
and the debugger will accept commands from the console.
The break point can be cleared by reissueing the B command.
If the B command is issued without arguments, the B c