Previous Page TOC Index Next Page

Chapter 36

Java debugging

Bugs are an unfortunate fact of life in software design. Similiar 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 might be familiar with in other professional development systems, but it does make the task of finding and exterminating bugs in your Java programs much easier.

JDB Commands

In this 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 (see Listing 36.1), the range of commands available in JDB is extensive.

> 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

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 to gain hands-on experience through using it. With this in mind, 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 of 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. On the supplied CD-ROM, there are 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 AppletViewer to load the applet.

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 our debugging session, we need first to compile our 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!).

In order to compile your program with debugging information enable, 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 allows the debugger to reference objects and line numbers in a program by source code names instead of 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 a applet in our example, we will use the AppletViewer program supplied in the Java Development 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, we will not start JDB in the preceding manner. However, for future reference, after invoking the debugger, using JDB on a Java application is identical to using it on an applet.

With that important distinction covered, we start the AppletViewer with the following command:

C:\java\classes\AddNumbers> appletviewer -debug StartApplet.html

The -debug flag specifies to the AppletViewer that it should start up in JDB instead of directly executing the AddNumbers class.

Once AppletViewer loads, it will open its applet window and display 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 us from doing anything that you might 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 our applet. In this case, it is 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 (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 asfollows:

> 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) and 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.

Figure FIGURE 36.1.

A view of the running applet.

Hmm—unless we learned math differently than the rest of you, 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 do exactly that. 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, this command tells JDB to stop when the method ComputeSum in the class AddNumbers is entered. This is convenient to do because the computation part of method that we are interested in is very close to the start of the method. If instead 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 instance, look at the handleEvent method of AddNumbers class. If we wanted to stop at the if statement where the program was checking for a push of the number 2 button, we would have entered the fol-lowing:

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 of 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 in order 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, but if you needed to clear the breakpoint we just set, the following would be entered:

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 along 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 only is called 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 in the above output, when you pressed the 2 button the debugger stopped at the ComputeSum method as we instructed it to. 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.

We know we are stopped in ComputeSum method, but let’s get a better sense of our bearings and refresh our memory by looking at where this is in the source code. Fortunately, this line number information is stored in the class when you compile with the -g option and we can access this by using the this list command of JDB 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]

Note that as expected, 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, OK?

Examining Objects

First, let’s check our operands for the computation to make sure they are correct. JDB provides three commands to display the contents of objects: locals, print, and dump. locals displays the current values of all of the objects defined in the local scope. print and dump 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 need 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 on 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, we need to 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. Let’s see the following 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 AppletViewer 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, it also is possible to look at the local objects in previous stack frames (which consist of all of the methods that either called ComputeSum or called a method that called ComputeSum, and so on). For example, let’s look at the following 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 up 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 back in the ComputeSum’s 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 of the methods in a class. For example, examining 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 of the classes that are 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 might become a concern. JDB’s memory command enable 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 15, “Threads and Multithreading,” Java applets have multiple threads of execution. Using JDB and the threads command, we can view these threads asfollows:

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 the 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 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 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

In order 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 of the paths contained in it. If that path doesn’t contain your source file, JDB will be unable to display the source for your program.

This wasn’t a problem for us because our search path contained the current directory, but if you set up your applet or application and the source is located in a directory outside of the search path, you’ll need 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. So to add the path \myclasses to the preceding path, we would 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

In our debugging section we had 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, while 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. catch, similar to a breakpoint, enables you to trap exceptions and stop the debugger. This is useful when debugging because it becomes 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. In order to trap any exception (the Exception exception base class), the following is done:

AWT-Callback-Win32[1] catch Exception

AWT-Callback-Win32[1]

ignore 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. The cont command does just that:

AWT-Callback-Win32[1] cont

As you can see, the program has resumed execution and the JDB prompt will not return until a breakpoint or exception is reached.

Leaving JDB Using the exit Command

Although it may be obvious to you already, there is one final command that comes in handy once in any debugging session. The exit command lets you out of the debugger and backinto DOS.

Summary

In this chapter, you have learned through hands-on experience about Sun’s Java debugger, JDB. 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 shorter amounts of time. Patience is definitely a virtue in software debugging.


Previous Page TOC Index Next Page