All Categories :
Java
Chapter 28
Java Debugging
by Tim Park
CONTENTS
Bugs are an unfortunate fact of life in software design. Similar
to most development environments, Sun has included a debugger
with the Java Development Kit (JDK) to help you fix your Java
applets and applications. JDB (short for Java DeBugger) isn't
a fancy visual debugging environment like the debuggers you may
be familiar with from other professional development systems,
but it does make the task of finding and exterminating bugs in
your Java programs much easier.
Most of this chapter shows you how to use JDB to debug your programs.
The last section uses Symantec's Café to show you how to
debug your Java programs using Café's very popular visual
environment.
In the first part of the chapter, we use a simple Java applet
to explain how to use JDB to help you debug your programs. As
you can see from the output of the JDB help
command in Listing 28.1, the range of commands available in JDB
is extensive.
Listing 28.1. Output from the JDB help
command.
> help
** command list **
threads [threadgroup] - list threads
thread <thread id> - set default thread
suspend [thread id(s)] - suspend threads (default: all)
resume [thread id(s)] - resume threads (default: all)
where [thread id] | all - dump a thread's stack
threadgroups - list threadgroups
threadgroup <name> - set current threadgroup
print <id> [id(s)] - print object or field
dump <id> [id(s)] - print all object information
locals - print all local variables in current stack frame
classes - list currently known classes
methods <class id> - list a class's methods
stop in <class id>.<method> - set a breakpoint in a method
stop at <class id>:<line> - set a breakpoint at a line
up [n frames] - move up a thread's stack
down [n frames] - move down a thread's stack
clear <class id>:<line> - clear a breakpoint
step - execute current line
cont - continue execution from breakpoint
catch <class id> - break for the specified exception
ignore <class id> - ignore when the specified exception
list [line number] - print source code
use [source file path] - display or change the source path
memory - report memory usage
gc - free unused objects (gc means garbage collection)
load classname - load Java class to be debugged
run <class> [args] - start execution of a loaded Java class
!! - repeat last command
help (or ?) - list commands
exit (or quit) - exit debugger
>
Using JDB to Debug Your Program
From our experience, the easiest way to learn JDB is by using
it. Without further ado, then, let's use JDB to debug the simple
class that follows. We chose a simple class for the benefit of
people skipping ahead to learn how to use the basic features of
the debugger. But don't worry if you've read all the previous
chapters of this book-we'll still cover all the advanced features
of JDB!
AddNumbers is a simple class
that implements both the user interface and the algorithm to add
two numbers together. Well, at least this is what it's supposed
to do. In reality, the class has a simple bug that will be located
using JDB (see Listing 28.2).
On the CD-ROM that accompanies this book, you will find two Java
classes: StartApplet, which
is the requisite subclass of the Applet
class that instantiates the AddNumbers
class, and StartApplet.html,
which is used by the applet viewer to load the applet. Use the
bundled installation program to install the files for this chapter
onto your hard drive.
Listing 28.2. The AddNumbers.java
file-complete with bug.
import java.awt.*;
public class AddNumbers extends Frame {
int LeftNumber = 5;
int RightNumber = 2;
TextArea taResult;
public AddNumbers() {
setTitle("JDB Sample Java Program");
setLayout(new BorderLayout());
Panel p = new Panel();
p.add(new Button("5"));
p.add(new Button("1"));
add("West", p);
Panel g = new Panel();
g.add(new Button("2"));
g.add(new Button("3"));
add("East", g);
taResult = new TextArea(2,1);
taResult.setEditable(false);
add("South", taResult);
pack();
resize(300,200);
show();
}
public void ComputeSum () {
int Total = LeftNumber - RightNumber;
String ConvLeft = String.valueOf(LeftNumber);
String ConvRight = String.valueOf(RightNumber);
String ConvTotal = String.valueOf(Total);
taResult.setText(ConvLeft + " + " + ConvRight + " = " + ConvTotal);
}
public void paint(Graphics g) {
ComputeSum();
}
public boolean handleEvent (Event evt) {
switch (evt.id) {
// Was the termination button pressed?
case Event.WINDOW_DESTROY: {
// Yes! So exit gracefully.
System.exit(0);
return true;
}
default:
}
// Was the "5" button pressed?
if ("5".equals(evt.arg)) {
LeftNumber = 5;
ComputeSum();
return true;
}
// Was the "1" button pressed?
if ("1".equals(evt.arg)) {
LeftNumber = 1;
ComputeSum();
return true;
}
// Was the "2" button pressed?
if ("2".equals(evt.arg)) {
RightNumber = 2;
ComputeSum();
return true;
}
// Was the "3" button pressed?
if ("3".equals(evt.arg)) {
RightNumber = 3;
ComputeSum();
return true;
}
return false;
}
}
Compiling for JDB
Before starting the debugging session, you must first compile
the Java applet to include extra information needed only for debugging.
This information is needed so that the debugger can display information
about your applet or application in a human-comprehensible form
instead of a confusing wash of hexadecimal numbers. (Don't laugh-the
first debuggers required you to do this translation, so count
your lucky stars!)
To compile your program with debugging information enabled, change
to the \java\classes\AddNumbers
directory and issue the following commands:
C:\java\classes\AddNumbers> javac_g -g AddNumbers.java
C:\java\classes\AddNumbers> javac_g -g StartApplet.java
The javac_g compiler is functionally
identical to the javac compiler
used in previous chapters, except that it doesn't perform any
optimizations to your applet or application. Optimizations rearrange
the statements in your applet or application to make them faster.
This rearrangement makes it more difficult to conceptualize program
flow when you are debugging, so using javac_g
in conjunction with JDB is useful.
The -g command-line option
tells the compiler to include line number and object names in
the output file. This option allows the debugger to reference
objects and line numbers in a program by source code names instead
of using the Java interpreter's internal representations.
Setting Up a Debugging Session
The next step in debugging a Java application or applet is to
start JDB. There are two ways to do this, depending on whether
you are debugging an applet or an application. Because we are
debugging an applet in our example, we will use the applet viewer
program supplied in the Java Developers Kit to load JDB indirectly.
If we were trying to debug an application instead, we would use
the following command to start JDB:
C:\java\classes\AddNumbers> jdb MyApplication
Again, because we are debugging an applet in our example and not
an application, do not start JDB in the preceding manner. However,
for future reference, after you invoke the debugger, using JDB
on a Java application is identical to using it on an applet.
With that important distinction covered, start the applet viewer
with the following command:
C:\java\classes\AddNumbers> appletviewer -debug StartApplet.html
The -debug flag specifies
to the applet viewer that it should start up in JDB instead of
by directly executing the AddNumbers
class.
Once the applet viewer loads, it opens its applet window and displays
something similar to the following in the command-line window:
C:\java\classes\AddNumbers> appletviewer -debug AddNumbers.html
Initializing jdb...
0x139f2f8:class(sun.applet.Appletviewer)
>_
The first thing you should notice about JDB is that it is command-line
based. Although this makes the learning curve for JDB a little
more steep, it doesn't prevent you from doing anything you may
be familiar with in a visual debugging environment.
Before going further, examine the third line of the preceding
output. This indicates where the debugger is stopped in its execution
of the applet. In this case, it stopped during the execution of
Sun's applet.Appletviewer
class. This is logical, because applet.Appletviewer
is the class that is transparently loaded and executed to load
an applet. (See, you're learning things by using JDB already!)
The hexadecimal number that prefixes this on the third line is
the ID number assigned to the sun.applet.Appletviewer
object by the Java interpreter.(Aren't you glad now that you can
see the English version because you used the -g
option for javac?) The >
prompt on the fourth line indicates that there is currently no
default thread that we are watching-more on this later.
To understand the bug in AddNumbers,
we will start the applet running in the debugger as follows:
> run
run sun.applet.AppletViewer MA.html
running ...
main[1]
The debugger should open the applet's frame and start executing
it. Because the debugger and the applet are on different threads
of execution, you can interact with the debugger and the applet
at the same time. The preceding main[1]
prompt indicates that the debugger is monitoring the applet thread
(the main thread); the [1]
indicates that we currently are positioned at the topmost stack
frame on the method call stack (we'll explain what this means
later).
Our applet is supposed to take the number of the button pressed
on the left and add it to the number of the button pressed on
the right. Try this out: press some of the buttons and check the
applet's math.
Hmm-unless you learned math differently than we did, there
seems to be something wrong with the computation of the applet.
(Maybe we found another Pentium processor flaw?)
Basic Debugger Techniques
To find out what is going on, let's examine the ComputeSum()
method of the AddNumbers
class. We would like to stop directly in this method without having
to move slowly and tediously through the rest of the code.
Setting and Clearing Breakpoints
Fortunately, JDB has a set of commands called breakpoints
that let you stop directly. We'll use breakpoints to stop program
execution in the ComputeSum()
method; but first, press the 5 and 3 buttons on the applet to
make sure that you see the same things as we do when the program
is stopped. After pressing 5 and 3, type the following in the
debugger window:
main[1] stop in AddNumbers.ComputeSum
Breakpoint set in AddNumbers.ComputeSum
main[1]
As you probably can guess, the stop in
command tells JDB to stop when the ComputeSum()
method in the AddNumbers
class is entered. This is convenient to do because the computation
part of method we are interested in is very close to the start
of the method. If the statement was farther down in the method,
it would be tedious to manually move down to the statement of
interest every time we hit the breakpoint at the beginning of
the method. In this case, we would want to use the stop
at command in JDB.
The stop at command works
exactly like the stop in
command in JDB, except that you specify the line number you want
JDB to stop on instead of the method. For example, look at the
handleEvent() method of the
AddNumbers class. If you
want to stop at the if statement
where the program checked for a push of the number 2 button, enter
the following:
main[1] stop at AddNumbers:90
Breakpoint set in AddNumbers:90
main[1]
However, don't do this because we want to examine
the ComputeSum() method and
not handleEvent(). We can
verify this and see all the breakpoints currently set by using
the clear command as follows:
AWT-Callback-Win32[1] clear
Current breakpoints set:
AddNumbers:37
AWT-Callback-Win32[1]
As expected, there is only one breakpoint set. Note that it is
specified as AddNumbers:37
instead of as AddNumbers:ComputeSum.
JDB converts the command stop in AddNumbers.ComputeSum
to stop at AddNumbers:37
to make its internal bookkeeping easier.
Of course, the real use of the clear
command is to clear breakpoints when they have outgrown their
usefulness. Don't do this now, but if you need to
clear the breakpoint you just set, enter the following:
AWT-Callback-Win32[1] clear AddNumbers.
ComputeSum
Breakpoint cleared at AddNumbers.ComputeSum
AWT-Callback-Win32[1]
Let's get back to debugging our applet. When we left our applet,
it was still running and there was no prompt in the JDB window.
Why hasn't JDB stopped at ComputeSum()?
If you look at the applet code, you notice that the ComputeSum()
method is called only when you press a button in the applet. Press
the 2 button to provide this ComputeSum()
method call:
main[1]
Breakpoint hit: AddNumbers.ComputeSum (AddNumbers: 37)
AWT-Callback-Win32[1]
As you can see, when you press the 2 button, the debugger stops
at the ComputeSum() method
as instructed. Note that we are now in a different thread (as
shown by the change in prompts from main[1]
to AWT-Callback-Win32[1])
because the AWT windowing manager thread calls the handleEvent()
method in the AddNumbers
class when a button is pressed in the applet.
Although we know we are stopped in the ComputeSum()
method, let's get a better sense of our bearings and refresh our
memory by looking at where this method is in the source code.
Fortunately, the line-number information is stored in the class
when you compile with the -g
option. You can access this information by using the JDB list
command as follows:
AWT-Callback-Win32[1] list
33 }
34 35 public void ComputeSum() {
36
37 => int Total = LeftNumber - RightNumber;
38
39 String ConvLeft = String.valueOf(LeftNumber);
40 String ConvRight = String.valueOf(RightNumber);
41 String ConvTotal = String.valueOf(Total);
AWT-Callback-Win32[1]
As expected, this output shows that we are stopped in ComputeSum()
on the first statement-and as luck would have it, right before
the computation statement. The observant reader probably already
can tell what is wrong, but just pretend that it's a much more
complicated computation and that you can't, okay?
Examining Objects
First, let's check our operands for the computation to make sure
that they are correct. JDB provides three commands to display
the contents of objects: locals,
print, and dump.
The locals command displays
the current values of all of the objects defined in the local
scope. The print and dump
commands are very similar and are used to display the contents
of any object in any scope, including objects defined in the interface
for the class. The main difference is that dump
displays more information about complex objects (objects with
inheritance or multiple data members) than print
does.
Because LeftNumber and RightNumber
are class members, we'll have to use print
to display them, as follows:
AWT-Callback-Win32[1] print LeftNumber
this.LeftNumber = 5
AWT-Callback-Win32[1] print RightNumber
this.RightNumber = 2
AWT-Callback-Win32[1]
The operands seem to be exactly as we entered them in the applet.
Let's take a look at the local objects to get a feeling for where
we are by using the locals
command as follows:
AWT-Callback-Win32[1] locals
this = AddNumbers[0,0,300x200,layout=
java.awt.BorderLayout,
resizable,title=JDB Sample Java Program]
Total is not in scope.
ConvLeft is not in scope.
ConvRight is not in scope.
ConvTotal is not in scope.
AWT-Callback-Win32[1]
As expected, JDB is telling us that none of the local objects
have been instantiated yet, so none of the objects are within
the local scope yet. Let's move the execution of the method along
one statement so that we can see what the value of the computation
is. To do this, use the JDB step
command as follows:
AWT-Callback-Win32[1] step
AWT-Callback-Win32[1]
Breakpoint hit: AddNumbers.ComputeSum (AddNumbers:39)
AWT-Callback-Win32[1]
JDB moves the execution along one statement and stops. Doing this
also triggers another breakpoint, because we are still in AddNumbers.ComputeSum().
Look at the following output to determine how the computation
turned out:
AWT-Callback-Win32[1] locals
this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout,
resizable,title=JDB Sample Java Program]
Total = 3
ConvLeft is not in scope.
ConvRight is not in scope.
ConvTotal is not in scope.
AWT-Callback-Win32[1]
We see that Total was instantiated,
the addition carried out, and the result put in Total.
But wait: 5 + 2 doesn't equal 3! Take a look at the following
source code:
AWT-Callback-Win32[1] list
35 public void ComputeSum() {
36
37 int Total = LeftNumber - RightNumber;
38
39 => String ConvLeft = String.valueOf(LeftNumber);
40 String ConvRight = String.valueOf(RightNumber);
41 String ConvTotal = String.valueOf(Total);
42
43 taResult.setText(ConvLeft + " + " + ConvRight + " = " +
AWT-Callback-Win32[1]
Oops-a subtraction sign was used instead of an addition sign!
So much for finding another bug in the Pentium processor, but
congratulations-you've found your first applet bug in JDB.
Additional JDB Functions
We've found our bug, but don't quit out of the applet viewer yet.
We'll use it and the AddNumbers
class to demonstrate a few more features of JDB that you might
find useful in future debugging sessions.
Walking the Method Call Stack with JDB
In the previous section, we used the locals
JDB command to look at the objects in the current scope. Using
the JDB command up, you can
also look at the local objects in previous stack frames (which
consist of all the methods that either called ComputeSum()
or called a method that called ComputeSum(),
and so on). For example, look at the following output to see the
state of the handleEvent()
method right before it called the ComputeSum()
method:
AWT-Callback-Win32[1] up
AWT-Callback-Win32[2] locals
this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout,
resizable,title=JDB Sample Java Program]
evt = java.awt.Event[id=1001,x=246,y=28,
target=java.awt.Button[5,5,20x24,label=2],arg=2]
AWT-Callback-Win32[2]
As you can see, the handleEvent
stack frame has two objects in its local frame: the pointer to
this AddNumber instance and
the Event object passed to
handleEvent().
It's possible to use the up
command as many times as your method call stack is deep. To undo
the up function and return
to a higher method call in the stack, use the JDB down
command as follows:
AWT-Callback-Win32[2] down
AWT-Callback-Win32[1] locals
this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout,
resizable,title=JDB Sample Java Program]
Total = 3
ConvLeft is not in scope.
ConvRight is not in scope.
ConvTotal is not in scope.
AWT-Callback-Win32[1]
As expected, we are now back in the ComputeSum()
local stack frame.
Using JDB to Get More Information about Classes
JDB also has two functions for getting more information about
classes: methods and classes.
methods enables you display
all the methods in a class. For example, you can examine the AddNumbers
class with the methods command:
AWT-Callback-Win32[1] methods
void <init>()
void ComputeSum()
void paint(Graphics)
boolean handleEvent(Event)
AWT-Callback-Win32[1]
The classes function lists
all the classes currently loaded in memory. Here is partial output
from the execution of classes
on AddNumbers (the actual
output listed more than 80 classes):
AWT-Callback-Win32[1] classes
...
...
0x13a5f70:interface(sun.awt.UpdateClient)
0x13a6160:interface(java.awt.peer.MenuPeer)
0x13a67a0:interface(java.awt.peer.ButtonPeer)
0x13a6880:class(java.lang.ClassNotFoundException)
0x13a6ea8:class(sun.tools.debug.Field)
0x13a7098:class(sun.tools.debug.BreakpointSet)
0x13a7428:class(sun.tools.debug.Stackframe)
0x13a7478:class(sun.tools.debug.LocalVariable)
AWT-Callback-Win32[1]
Monitoring Memory Usage and Controlling finalize()
For some large applets or applications, the amount of free memory
may become a concern. JDB's memory
command enables you to monitor the amount of used and free memory
during your debugging session, as follows:
AWT-Callback-Win32[1] memory
Free: 2554472, total: 3145720
AWT-Callback-Win32[1]
JDB also lets you explicitly demand that the finalize()
method be run on all freed objects through the gc
(garbage collection) command. This is useful for proving that
your applet or application correctly handles deleted objects-which
can be difficult to prove normally with small applets and applications
because the finalize() methods
are normally called only when the applet or application has run
out of free memory.
Controlling Threads of Execution
As you know from Chapter 9, "Threads
and Multithreading," Java applets have multiple threads of
execution. Using JDB and the threads
command, you can view these threads as follows:
AWT-Callback-Win32[1] threads
Group sun.applet.AppletViewer.main:
1. (java.lang.Thread)0x13a3a00 AWT-Win32 running
2. (java.lang.Thread)0x13a2a58 AWT-Callback-Win32 running
3. (sun.awt.ScreenUpdater)0x13a2d98 Screen Updater running
Group group applet-StartApplet.class:
4. (java.lang.Thread)0x13a28f0 class running
AWT-Callback-Win32[1]
As you can see from this output, there are four threads of simultaneous
applet execution. Two correspond to the AWT window management
system (threads 1 and 2), one for updating the screen (thread
3), and one for the actual applet itself (thread 4).
JDB provides two commands for controlling the execution of threads:
suspend and resume.
Suspending a thread isn't very worthwhile in our simple example,
but in multithreaded applications it can be very worthwhile-you
can suspend all but one thread and focus on that thread.
But let's try suspend and
resume on our applet to get
a feel for their use. To suspend the AWT-Win32 thread, you should
note its ID from the threads
list and then use this information as the argument to suspend,
as follows:
AWT-Callback-Win32[1] threads
Group sun.applet.AppletViewer.main:
1. (java.lang.Thread)0x13a3a00 AWT-Win32 running
...
AWT-Callback-Win32[1] suspend 1
AWT-Callback-Win32[1] threads
Group sun.applet.AppletViewer.main:
1. (java.lang.Thread)0x13a3a00 AWT-Win32 suspended
2. (java.lang.Thread)0x13a2a58 AWT-Callback-Win32 running
3. (sun.awt.ScreenUpdater)0x13a2d98 Screen Updater running
Group group applet-StartApplet.class:
4. (java.lang.Thread)0x13a28f0 class running
AWT-Callback-Win32[1]
As expected, the AWT-Win32 thread is now suspended. Threads are
resumed in a completely analogous manner-with the resume
command, as follows:
AWT-Callback-Win32[1] resume 1
AWT-Callback-Win32[1] threads
Group sun.applet.AppletViewer.main:
1. (java.lang.Thread)0x13a3a00 AWT-Win32 running
2. (java.lang.Thread)0x13a2a58 AWT-Callback-Win32 running
3. (sun.awt.ScreenUpdater)0x13a2d98 Screen Updater running
Group group applet-StartApplet.class:
4. (java.lang.Thread)0x13a28f0 class running
AWT-Callback-Win32[1]
Using use to Point the Way to Your Java Source Code
To execute the list command,
JDB takes the line number and grabs the required lines of Java
from the source file. To find that source file, JDB reads your
CLASSPATH environmental variable
and searches all the paths contained in it. If that path doesn't
contain your source file, JDB is unable to display the source
for your program.
This wasn't a problem for this example because the search path
contained the current directory, but if you set up your applet
or application and the source is located in a directory outside
the search path, you'll have to use the use
command to add to your path. The use
command without any arguments displays the current search path
as follows:
AWT-Callback-Win32[1] use
\java\classes;.;C:\JAVA\BIN\..\classes;
AWT-Callback-Win32[1]
Appending a directory to the search path is, unfortunately, slightly
tedious. You have to retype the entire current path and
add the new path. To add the path \myclasses
to the preceding path, do the following:
AWT-Callback-Win32[1] use \java\classes;.;
C:\JAVA\BIN\..\classes;\myclasses
AWT-Callback-Win32[1]
Getting More Information about Your Objects with dump
Earlier in this chapter, you saw an example of how to display
an object's value using the print
command. In this section, we'll look at JDB's dump
command, which is a more useful display command for objects containing
multiple data members. The AddNumbers
class is a good example (note that this
in this case refers to the instantiation of AddNumbers
for our applet), as follows:
AWT-Callback-Win32[1] dump this
this = (AddNumbers)0x13a3000 {
ComponentPeer peer = (sun.awt.win32.MFramePeer)0x13a31b0
Container parent = null
int x = 0
int y = 0
int width = 300
int height = 200
Color foreground = (java.awt.Color)0x13a2bb0
Color background = (java.awt.Color)0x13a2b98
Font font = (java.awt.Font)0x13a31d0
boolean visible = true
boolean enabled = true
boolean valid = true
int ncomponents = 3
AWT-Callback-Win32[1] _
Contrast this with the output from print:
AWT-Callback-Win32[1] print this
this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout,
resizable,title=JDB Sample Java Program]
AWT-Callback-Win32[1]
As you can see, the dump
command displays the data members for the class but print
displays only the key attributes for the class.
Handling Exceptions with catch
and ignore
JDB has two functions for dealing with exceptions: catch
and ignore. The catch
function, similar to a breakpoint, enables you to trap exceptions
and stop the debugger. This is useful when debugging because it
is much easier to diagnose an exception when you know the conditions
under which it occurred. To catch an exception, simply type class
and the name of the exception class. To trap any exception (the
Exception base class), do
the following:
AWT-Callback-Win32[1] catch Exception
AWT-Callback-Win32[1]
The ignore function does
exactly the opposite of catch.
It squelches the specified class of exceptions raised by an applet
or application. The use of ignore
is completely analogous to catch,
as shown by the following:
AWT-Callback-Win32[1] ignore ArrayOutOfBoundsException
AWT-Callback-Win32[1]
Continuing Program Execution with cont
You may be wondering how to restart execution once you reach a
breakpoint and execution has stopped. Why, you use the cont
command, as shown here:
AWT-Callback-Win32[1] cont
The program resumes execution and the JDB prompt does not return
until a breakpoint or exception is reached.
Leaving JDB Using exit
Although it may be obvious to you already, there is one final
command that comes in handy at least once in any debugging session.
The exit command lets you
out of the debugger and back into DOS.
If you want to be a more efficient Java developer, it is hard
to find anything as important as adopting an integrated development
environment as your development platform. In our opinion, currently
the best such visual environment is Symantec's Café for
Java. In addition to excellent debugging support, it also features
project management, a visual GUI builder, and object-oriented
browsing of your Java classes. Chapter 5,
"Third-Party Development Tools," discusses other IDEs.
Loading the AddNumbers
Project
The first thing we need to do to use Symantec Café's debugger
is to load the project file for the AddNumbers
applet. This project file contains all the information needed
to build AddNumbers.java
and StartApplet.java. Use
the mouse to pull down the Project menu and select the Open option.
The Open Project dialog box appears. In this dialog box, type
the path or navigate to the directory containing the same AddNumbers
applet you used in the first part of this chapter. From this directory,
you should be able to see a file named AddNumbers.prj.
Select this file and click OK to load the project.
Building the AddNumbers
Project
The AddNumbers.prj project
should have come precompiled on the CD-ROM that accompanies this
book, but just in case, let's rebuild it in Café. Pull
down the Project menu again and select Build. Café pops
up a build status window and builds the files one by one.
Running the AddNumbers
Project
Before we jump into debugging the AddNumbers
project, let's run it first and make sure that it still adds numbers
wrong. To run it, pull down the Project menu and select Execute
Program (or press its accelerator key, Ctrl+F5).
Debugging the AddNumbers
Project
After you play with the AddNumbers
applet for a while and confirm that it is broken, stop it at the
applet's execution. Using the Debug menu, select Start/Restart
Debugging. This command should bring up five windows: a source
code window displaying StartApplet.java,
a Data/Object window, a Call window, a Thread window, and an Output
window. A thorough coverage of all these windows would require
more than a single chapter. For the purposes of this chapter,
we'll explain what all the windows do, but focus on the source
code window and the Data/Object window-these are the only windows
you'll use for 90 percent of your debugging tasks anyway.
At this point, the debugger is stopped at the first program statement
and is waiting for your command. Suppose that from your last test
run, you have guessed that the error lies somewhere within the
ComputeSum() method in the
AddNumbers class. Let's set
a breakpoint here using Café and run until we get to this
point in the program.
To set a breakpoint, you must first open the AddNumbers.java
file. With the AddNumbers
file in the editor, move down in the file until you reach the
ComputeSum() method. Pull
down the Window menu in Café and make sure that the Debug
Toolbox option is selected. Next, move the cursor down to line
37, which is the computation of Total.
Finally, find the Debug toolbar on your icon bar, which looks
like the bar in Figure 28.1.
Figure 28.1 : Café's debug toolbar.
The breakpoint toggle button is the one with the red and green
flags on it. Click this button to toggle the breakpoint on for
the computation line. A little red diamond should appear next
to that line (see Figure 28.2).
Figure 28.2 : Setting a breakpoint in Café.
With our breakpoint set, we're now ready to let the program execute
freely until it reaches the breakpoint. To do this, pull down
the Debug menu and select Go Until Breakpoint. This action starts
the applet viewer and the AddNumbers
applet runs until it reaches the ComputeSum()
method as part of the redraw. Execution stops at the line on which
we put the breakpoint-as we expect (see Figure 28.3). Notice,
however, that Café doesn't execute the line we set as the
target of the breakpoint.
Figure 28.3 : Café stopped at a breakpoint.
Café's Call window (shown in Figure 28.4) shows the stack
of method calls that have been made to reach this point in program
execution. We know that the ComputeSum()
method was reached during the repaint of the window because it
was called from the AddNumbers
paint() method.
Figure 28.4 : Cafe's Call window.
Café's Breakpoint window (shown in Figure 28.5) shows which
breakpoint caused the stop in execution. In this example, there
is only one breakpoint, so this information isn't very helpful-but
for bigger debugging situations, the Breakpoint window is more
useful as a central reference for all the breakpoints you have
set in your program.
Figure 28.5 : Café's Breakpoint window.
The Thread window shows all the threads in operation for your
program. Note that its output is exactly the same, if in a somewhat
more readable format, as what we saw when we were using JDB.
But what we're really interested in is the Data/Object window
(shown in Figure 28.6). This window contains the current values
of all the data members for local objects. In looking at the values
of lLeftNumber and lRightNumber,
we can tell that the problem isn't caused by the operands to the
computation because they are what we expect them to be.
Figure 28.6 : Café's Data/Object window before the Total computation.
Let's move execution ahead one line so that we can see the result
of the execution. To do this, press the F10 key or click the button
in the Debug toolbar with the foot "stepping into" a
yellow hole. The "stepping into" button differs from
the "stepping over" button (the next button on the Debug
bar) in that "stepping into" shows the execution of
any and all function calls incurred on this line, while "stepping
over" shows only the output of the function.
Looking at the Data/Object window, we can now tell that something
is wrong with the computation of Total:
5 + 2 certainly doesn't equal 3, as shown in the display of the
Data/Object window in Figure 28.7. Hopefully, with this information,
the bug is now obvious to you.
Figure 28.7 : Café's Data Objects window after the Total computation.
To complete the debug cycle, take Café out of debugging
mode using the Debug | Stop Debugging menu option. Then change
the - operator in the Total
computation to a + and rebuild using the Project | Build menu
option. Run the program again to show that the program has been
fixed.
This chapter helped you learn how to debug by giving you hands-on
experience with Sun's Java debugger, JDB, and Symantec's visual
environment, Café. Debugging is a learned skill; don't
be discouraged if it takes you a long time to debug your first
applet or application. As you gain experience doing it, you'll
start to recognize the effects of different classes of bugs and
be able to solve each bug in a shorter amount of time. Patience
is definitely a virtue in software debugging.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.