Description
Our goal in this project learn to use the jdb debugger by correcting a slightly buggy program that generates
a form letter to Mom and Dad. Begin by copying the example LetterHome.java from the Examples page
on the class website to your own account space. Also copy the auxiliary files letter1.dat, letter2.dat,
and letter3.dat. Read the (rather long) comment block at the beginning of the program explaining its
intended operation. Compile and run it as described in the comments, and then note that the program does
not behave as expected. This program reads input from a file specified on the command line – something
we will see more of in future assignments. To run the program on the input file letter1.dat for instance,
do
% java LetterHome letter1.dat
Long ago program errors of various kinds became known as bugs. Go to
http://www.computerhistory.org/tdih/September/9/
to see the first computer bug. Debugging is the process of locating and correcting these errors. For complex
programs, this can be a slow and expensive process that requires as much effort as writing the program in
the first place. Program errors can be categorized into three basic types. A syntax error occurs when a
program statement does not follow the proper rules of form defined in the language specification. Typically
these are misspellings of reserved words or identifiers, or the improper use or placement of punctuation
characters, like the semicolon, comma, parentheses, brackets or braces. Syntax errors are usually revealed
at compile time. The javac compiler provides fairly clear messages to help the programmer find such errors.
Runtime errors occur when the program is executed using certain sets of data that result in some illegal
operation being performed, such as division by zero. The Java Virtual Machine (JVM) provides descriptive
error messages at run time to help track these types of bugs. The third, and most subtle, class of errors is
logic errors. These are the result of using a faulty algorithm to solve the problem. Some incorrect steps
that result in wrong answers are performed, but no error messages are produced to help pinpoint the
problem. The only way to detect logic errors is to notice that the program output is simply wrong. Extensive
time spent on debugging usually means that insufficient time was spent on carefully specifying, organizing,
and structuring the program. If the design is poor, then the resulting program is often a structural mess,
with convoluted, hard to understand logic. Devoting careful attention to the design phases helps reduce the
amount of debugging that must be done. Even when appropriate care is used however, bugs can still creep
in.
A time honored method to debug both Runtime errors and logic errors is to place print statements (i.e.
System.out.println() in Java) at judicious points in the program to track the values of critical variables.
The programmer can then detect the point or points during execution where those variables depart from
their expected values, helping to pinpoint the error. The programs we’ve studied so far have been
sufficiently simple that we could manually trace each step, following the values of all variables at each
point in time, and thereby understanding the program fully. With large complex programs, this becomes
impractical or impossible. A debugger is a software tool that performs such a trace automatically, allowing
one to step through execution at any desired speed. One can follow the precise logical pathway taken by
the program through loops and conditionals, and monitor any or all variables along the way. The Java
debugger we will use in this assignment is called jdb. To use it you must first compile your program using
the –g flag:
2
% javac –g myProgram.java
This inserts special instructions into the executable myProgram.class that will be used by jdb to trace
execution. To run jdb do
% jdb myProgram command_line_arguments
This project was designed to read input from a file (and hence the need for command line arguments)
because jdb cannot (easily) be run on programs that read from standard input. This is because jdb itself
reads from standard input, and the program can’t tell which are debugger commands and which are inputs
to the program.
Perform the following steps to complete the assignment.
Compile the program for later use with jdb: % javac –g LetterHome.java
Run the program normally using letter1.dat as input: % java LetterHome letter1.dat
Observe that there are at least two errors, since there are two departures from the expected output.
(Read the comments in the program to see what that output should be.)
Run the program in jdb using letter1.dat as input: % jdb LetterHome letter1.dat. You
will see something about jdb being initialized, then you should see the command prompt “>”.
Type help to see a list of debugger commands along with a short description of what each command
does.
Set a breakpoint at the start of the program so that you can step through the program when it is run.
A breakpoint is a place in the program where the debugger will pause and await further instructions.
The debugger commands stop in and stop at are used to set breakpoints.
o stop in ClassName.MethodName (sets a breakpoint at the beginning of Method).
o stop at ClassName:LineNumber (sets a breakpoint at LineNumber).
Thus, to set an initial breakpoint, do: stop in LetterHome.main. You will see a message saying
that the stop will be set when the class is loaded.
Run the program by typing: run. The program is now running, but temporarily paused at the
breakpoint you set above. To see where you are in the source code type list. This shows the
source code surrounding the breakpoint, and the next instruction to be executed, which is indicated
by the symbol =>. You can see that execution is halted right before the first instruction in function
main(), namely Scanner in = new Scanner(new File(args[0]));
Recall that the first program error was that a valid sentence code was not accepted as valid. Type
list 125 to see the code that checks for a valid sentence code. Set a breakpoint here by typing
stop at LetterHome:125. (It will be helpful to have a listing of the source code with line numbers
included. This can be done by running vi or emacs in a separate window.)
Run the program up to the next breakpoint by typing cont. Type locals to see the values of all
local variables (i.e. those local to function main()) at this time. You can advance the program
exactly one step by typing next. You can also see the value of a single variable by doing print
variableName. Examine the values of each of the local variables at this point, and notice that when
you step past the breakpoint, execution flows into the true branch of the test for a valid sentence
code. Why was the conditional evaluated to be true? Evaluate it yourself given the values of the
variables. You should now see the program error. (Hint: the valid sentence codes are 1 through 5
inclusive.)
Recall that the second program error was the weather modifier, i.e. “foggy” instead of “great”. Type
list 140 to see that the weatherModifier() method is called on line 142. Set a breakpoint at
that line by doing stop at LetterHome:142, and type cont to continue execution up to the next
breakpoint.
3
Notice that the program does not hit the breakpoint at line 142. Instead it hits the one at line 125
again. To understand why, look at the input file letter1.dat, and observe that the sentence code
2 (for the weather sentence) appears on line 3. Thus line 142 is not executed until the third iteration
of the surrounding while loop, while line 125 is executed on every iteration. Therefore we have to
hit 125 three times before we can hit 142. We’ve only hit 125 twice so far. Type cont to hit the
breakpoint at 125 yet again, then cont one more time to finally hit the breakpoint at 142. You can
also just type next again and again to watch the loop go around, instead of cont which always
takes you up to the next breakpoint. You can also do clear LetterHome:125 to delete the
breakpoint at 125, and then cont takes you directly to 142.
Type list to see that the upcoming instruction is the call to weatherModifier(). Examine the
value of its argument modifierCode at this point by doing print modifierCode.
Recall that the next command causes the next instruction in the current method (namely main())
to be executed. Thus next causes the debugger to step over method calls. But this is not what we
want, since the error, whatever it is, lies inside weatherModifier(). Instead type the command
step, which follows execution into a method. Now do list again and observe that we have entered
the method weatherModifier() and are about to execute its first instruction. Remember, next
goes over function calls and step goes into them. Another variation is step up, which advances
to the next instruction in the calling function (i.e. the method that called the one we’re presently in.)
In other words, step up causes the current method to finish, then pause right after we return to the
calling function.
Type locals to monitor the value of the method parameter m. Observe that its value is identical to
the function argument modifierCode which we examined above, just as one would expect.
Now do step a few times to follow execution within weatherModifier(). What path through the
conditionals does it take? What path did you expect it to take, given the value of m above? You
should now see the program error. If not, type step a few more times, do locals again and note
that the value the string variable word is “foggy”, not “great” as expected. Why is that?
Run the program a few more times in jdb and play with other debugger commands. Type man jdb
from the Unix prompt to see more detail on how the debugger commands work.
What to turn in
Write a one sentence description of each of the two errors you found in the above exercise, then fix the
errors. Compile the corrected program, and run it again on the same input letter1.dat. Observe that
there is still an error somewhere. Use jdb to track down that error, write a one sentence description of it,
then fix it. Test the corrected program on letter1.dat, letter2.dat and letter3.dat. Delete the
comment block at the beginning of the file and replace it with the your usual identification block. Include
in that comment your neatly written descriptions of the three errors found. Submit the altered file
LetterHome.java to the assignment name lab5, then check the submission in the usual ways.