Consider the C program in Figure 1. The program is used to illustrate a common error made by C programmers. The object of the program is to change the lower case "t" to upper case in the string pointed to by charp and then write the character string to the file indicated by argument 1. The bug shown is that the character "T" is stored in the pointer charp instead of the string pointed to by charp. Executing the program produces a core file because of an out of bounds memory reference.
ADB is invoked by:
adb a.out core
The first debugging request:
$c
is used to give a C backtrace through the
subroutines called.
As shown in Figure 2
only one function (main) was called and the
arguments
argc
and
argv
have octal values 02 and
0177762 respectively.
Both of these values look
reasonable; 02 = two arguments, 0177762 = address on stack
of parameter vector.
The next request:
$C
is used to give a C backtrace plus an interpretation
of all the local variables in each function and their
values in octal.
The value of the variable
cc
looks incorrect
since
cc
was declared as a character.
The next request:
$r
prints out the registers including the program
counter and an interpretation of the instruction at that
location.
The request:
$e
prints out the values of all external variables.
A map exists for each file
handled by
ADB.
The map for the
a.out
file is referenced by ? whereas the map for
core
file is referenced by /.
Furthermore, a good rule of thumb is to use ? for
instructions and / for data when looking at programs.
To print out information about the maps type:
$m
This produces a report of the contents of the maps.
More about these maps later.
In our example, it is useful to see the
contents of the string pointed to by
charp.
This is done by:
*charp/s
which says use
charp
as a pointer in the
core
file
and print the information as a character string.
This printout clearly shows that the character buffer
was incorrectly overwritten and helps identify the error.
Printing the locations around
charp
shows that the buffer is unchanged
but that the pointer is destroyed.
Using ADB similarly, we could print information about the
arguments to a function.
The request:
main.argc/d
prints the decimal
core
image value of the argument
argc
in the function
main.
The request:
*main.argv,3/o
prints the octal values of the three consecutive
cells pointed to by
argv
in the function
main.
Note that these values are the addresses of the arguments
to main.
Therefore:
0177770/s
prints the ASCII value of the first argument.
Another way to print this value would have been
*"/s
The " means ditto which remembers the last address
typed, in this case main.argc ; the * instructs ADB to use the address field of the
core
file as a pointer.
The request:
.=o
prints the current address (not its contents) in octal which has been set to the address of the first argument.
The current address, dot, is used by ADB to
"remember" its current location.
It allows the user
to reference locations relative to the current
address, for example:
.-10/d
Consider the C program illustrated in Figure 3. This program calls functions f, g, and h until the stack is exhausted and a core image is produced.
Again you can enter the debugger via:
adb
which assumes the names
a.out
and
core
for the executable
file and core image file respectively.
The request:
$c
will fill a page of backtrace references to
f, g,
and
h.
Figure 4 shows an abbreviated list (typing
DEL
will terminate the output and bring you back to ADB request level).
The request:
,5$C
prints the five most recent activations.
Notice that each function (f,g,h) has a counter of the number of times it was called.
The request:
fcnt/d
prints the decimal value of the counter for the function
f.
Similarly
gcnt
and
hcnt
could be printed.
To print the value of an automatic variable,
for example the decimal value of
x
in the last call of the function
h,
type:
h.x/d
It is currently not possible in the exported version to print stack frames other than the most recent activation of a function.
Therefore, a user can print everything with
$C or the occurrence of a variable in the most recent call of a function.
It is possible with the $C request, however, to print the stack frame
starting at some address as address$C.
Consider the C program in Figure 5. This program, which changes tabs into blanks, is adapted from Software Tools by Kernighan and Plauger, pp. 18-27.
We will run this program under the control of ADB (see Figure 6a) by:
adb a.out -
Breakpoints are set in the program as:
address:b [request]
The requests:
settab+4:b
fopen+4:b
getc+4:b
tabpos+4:b
set breakpoints at the start of these functions.
C does not generate statement labels.
Therefore it is currently not possible to plant breakpoints at locations
other than function entry points without a knowledge of the code
generated by the C compiler.
The above addresses are entered as
symbol+4
so that they will appear in any
C backtrace since the first instruction of each function is a call
to the C save routine
(csv).
Note that some of the functions are from the C library.
To print the location of breakpoints one types:
$b
The display indicates a
count
field.
A breakpoint is bypassed
count -1
times before causing a stop.
The
command
field indicates the ADB requests to be executed each time the breakpoint is encountered.
In our example no
command
fields are present.
By displaying the original instructions at the function
settab
we see that
the breakpoint is set after the jsr to the C save routine.
We can display the instructions using the ADB request:
settab,5?ia
This request displays five instructions starting at
settab
with the addresses of each location displayed.
Another variation is:
settab,5?i
which displays the instructions with only the starting address.
Notice that we accessed the addresses from the a.out file with the ? command. In general when asking for a printout of multiple items, ADB will advance the current address the number of bytes necessary to satisfy the request; in the above example five instructions were displayed and the current address was advanced 18 (decimal) bytes.
To run the program one simply types:
:r
To delete a breakpoint, for instance the entry to the function
settab,
one types:
settab+4:d
To continue execution of the program from the breakpoint type:
:c
Once the program has stopped (in this case at the breakpoint for
fopen),
ADB requests can be used to display the contents of memory.
For example:
$C
to display a stack trace, or:
tabs,3/8o
to print three lines of 8 locations each from the array called
tabs.
By this time (at location
fopen)
in the C program,
settab
has been called and should have set a one in every eighth location of
tabs.
We continue execution of the program with:
:c
See Figure 6b.
Getc
is called three times and the contents of the variable
c
in the function
main
are displayed
each time.
The single character on the left hand edge is the output from the C program.
On the third occurrence of
getc
the program stops.
We can look at the full buffer of characters by typing:
ibuf+6/20c
When we continue the program with:
:c
we hit our first breakpoint at
tabpos
since there is a tab following the
"This" word of the data.
Several breakpoints of
tabpos
will occur until the program has changed the tab into equivalent blanks.
Since we feel that
tabpos
is working,
we can remove the breakpoint at that location by:
tabpos+4:d
If the program is continued with:
:c
it resumes normal execution after ADB prints
the message
a.out:running
The UNIX quit and interrupt signals
act on ADB itself rather than on the program being debugged.
If such a signal occurs then the program being debugged is stopped and control is returned to ADB.
The signal is saved by ADB and is passed on to the test program if:
:c
is typed.
This can be useful when testing interrupt
handling routines.
The signal is not passed on to the test program if:
:c 0
is typed.
Now let us reset the breakpoint at
settab
and display the instructions located there when we reach the breakpoint.
This is accomplished by:
settab+4:b settab,5?ia *
It is also possible to execute the ADB requests for each occurrence of the breakpoint but
only stop after the third occurrence by typing:
getc+4,3:b main.c?C *
This request will print the local variable
c
in the function
main
at each occurrence of the breakpoint.
The semicolon is used to separate multiple ADB requests on a single line.
Warning:
setting a breakpoint causes the value of dot to be changed;
executing the program under ADB does not change dot.
Therefore:
settab+4:b .,5?ia
fopen+4:b
will print the last thing dot was set to
(in the example fopen+4)
not
the current location (settab+4)
at which the program is executing.
A breakpoint can be overwritten without first deleting the old breakpoint.
For example:
settab+4:b settab,5?ia; ptab/o *
could be entered after typing the above requests.
Now the display of breakpoints:
$b
shows the above request for the
settab
breakpoint.
When the breakpoint at
settab
is encountered the ADB requests are executed.
Note that the location at
settab+4
has been changed to plant the breakpoint;
all the other locations match their original value.
Using the functions,
f, g
and
h
shown in Figure 3,
we can follow the execution of each function by planting non-stopping
breakpoints.
We call ADB with the executable program of Figure 3 as follows:
adb ex3 -
Suppose we enter the following breakpoints:
h+4:b hcnt/d; h.hi/; h.hr/
g+4:b gcnt/d; g.gi/; g.gr/
f+4:b fcnt/d; f.fi/; f.fr/
:r
Each request line indicates that the variables are printed in decimal
(by the specification d).
Since the format is not changed, the d can be left off all but
the first request.
The output in Figure 7 illustrates two points. First, the ADB requests in the breakpoint line are not examined until the program under test is run. That means any errors in those ADB requests is not detected until run time. At the location of the error ADB stops running the program.
The second point is the way ADB handles register variables. ADB uses the symbol table to address variables. Register variables, like f.fr above, have pointers to uninitialized places on the stack. Therefore the message "symbol not found".
Another way of getting at the data in this example is to print
the variables used in the call as:
f+4:b fcnt/d; f.a/; f.b/; f.fi/
g+4:b gcnt/d; g.p/; g.q/; g.gi/
:c
The operator / was used instead of ?
to read values from the core file.
The output for each function, as shown in Figure 7, has the same format.
For the function f, for example, it shows the name and value of the
external
variable
fcnt.
It also shows the address on the stack and value of the
variables
a, b
and
fi.
Notice that the addresses on the stack will continue to decrease
until no address space is left for program execution
at which time (after many pages of output)
the program under test aborts.
A display with names would be produced by requests like the following:
f+4:b fcnt/d; f.a/"a="d; f.b/"b="d; f.fi/"fi="d
In this format the quoted string is printed literally and the d
produces a decimal display of the variables.
The results are shown in Figure 7.