Debugging Java Programs


The PJEE is a developer tool for testing Java software. This includes running Java software to observe its behavior and debugging Java software to explore the relationships between the source code's structure, the compiled code's behavior and the PJEE's capabilities.

Some of the debugging techniques described here can be used to debug Java software on a PersonalJava device as well as on the PJEE. But for most debugging tasks the PJEE will be more convenient and have more debugging resources (e.g. symbol tables) than a PersonalJava device.

Note: The debugging support in the PJEE is based on the Java Virtual Machine Debugger Interface (JVMDI). To support compatibility with the PJEE, third-party developer tools must support this interface.

The following sections describe the debugging resources available for PersonalJava software development and introduce their basic usage.

Using jdb

The JDK 1.2.x includes the jdb command-line debugger which can be used to debug Java programs running on the PJEE. The JDK must be installed on either the same system as the PJEE or on a system connected over an IP network.

The following steps describe how to use jdb to debug a Java applet running on the PJEE.

  1. Run the debug version of the PersonalJava application launcher. Use the pjava_g version with the -debug option to enable full debugging support.

    % pjava_g -ss1024k -debug sun.tools.agent.EmptyApp
    

    EmptyApp is a placeholder Java application which is used by jdb when it is launched without an initial class.

    Note: The default Java stack size may be too small for debugging purposes. So it may be necessary to increase the stack size with the -ssnum option.

  2. Record the session password identifier:

    % Agent password: identifier
    

  3. Run jdb with the session password identifier string and an optional remote host address.

    % jdb -host pjava_host -password identifier
    

    pjava_host is the host name or IP address of the system running the PJEE. identifier is the session password identifier displayed by the PersonalJava application launcher in the previous step.

  4. Load the AppletViewer class:

    > load sun.applet.AppletViewer
    

  5. Set a break point in the applet:

    > stop in HelloWorldApplet.paint
    

  6. Run the AppletViewer class with an additional URL argument that indicates an HTML page that contains the applet:

    > run sun.applet.AppletViewer HelloWorldApplet.html
    

At this point, execution should be stopped at the first line in the paint method, and jdb should be connected to the PJEE for debugging. See jdb for a list of debugging commands or type help at the jdb command line prompt.

Dumping a Thread Stack

During a jdb session, the currently executing thread stack can be dumped with a platform-specific key sequence:

PlatformKey Sequence
Microsoft Windows 95/NT CONTROL-break
Solaris CONTROL-\

Generating Diagnostic Output

The most basic method for generating useful data from a Java application at runtime is to use the println method. This technique displays a text stream on the standard output. If the PJAE implementation is running on a device, then the device must be attached to a development system with a serial cable so that the standard output stream can be captured with a communications terminal program.

Similarly, the best way to get runtime information about a native method is to use printf() to format data for display on a terminal window or through a serial port.

Debugging Native Methods

Debugging native methods requires techniques that are different from those used for Java software. There are two basic approaches:

Native Debugger Notes

A native debugger used with the PJEE should be compatible with the symbol tables generated by the compiler used to build the PJEE. The Solaris version of the PJEE was built with the GNU C Compiler, version 2.7.3 and the Microsoft Windows 95/NT version was built with Microsoft Visual C++, version 5.0.

The PersonalJava Environment Software (PJES) includes a build environment for building binary executable versions of the PJAE like the PJEE. The PJES build environment has a mechanism for including native libraries in the list of object files that are statically linked with the PJAE binary executable. This procedure can be used to include native methods for applications that will be bundled with an implementation of the PJAE on a device. Debugging statically linked native code is much easier than debugging native code that is included in a shared library.

See the section Adding Object Files in the PersonalJava Porting Guide for a description of how to include object files in the PJES build environment.

A gdb-based Example on Solaris

Versions of the PersonalJava invocation tools that include symbol tables for debugging Java software with native debuggers like gdb(1) must be built from the PersonalJava source. The following Solaris-based procedure outlines the steps involved in using gdb to debug a simple Java application with a native method. The emphasis here is on the mechanics of debugging rather than special debugging techniques for different programming problems.

  1. Explicitly define the CLASSPATH and LD_LIBRARY_PATH environment variables. gdb uses the debugging executable version instead of front-end shell script, which usually sets these environment variables.

    % setenv CLASSPATH .:PJEE-dir/lib/classes.zip
    % setenv LD_LIBRARY_PATH .:PJEE-dir/lib/sparc
    

  2. Launch gdb with pjava_g. Be sure to use the debugging executable version of the PersonalJava application launcher instead of the front-end shell script.
    % gdb PJEE-dir/bin/sparc/pjava_g
    

  3. Set a break point in main().
    (gdb) tbreak main
    

  4. Launch pjava_g with the HelloWorld class. This loads the symbol tables for the pjava_g application launcher.
    (gdb) run HelloWorld
    

  5. Set a break point in sysAddDLSegment().
    (gdb) break sysAddDLSegment
    
    Note: sysAddDLSegment() is an internal VM function that dynamically links shared libraries for native methods. It is used here solely as a convenient reference for setting a break point after a shared library has been linked. This technique may not apply to other VM implementations.

  6. Allow execution to proceed to the point where the shared library that contains the native method implementation has been linked. This procedure involves one or more cycles of the following steps:

    1. Continue execution to the break point at the beginning of sysAddDLSegment().

      (gdb) continue
      

    2. Continue again until the function returns.

      (gdb) finish
      

    3. Determine whether the shared library has been loaded.

      (gdb) share
      

      • If not, repeat the steps above.

      • If so, set a break point in the shared library, according to the instructions below.

  7. Clear the break point at the beginning of sysAddDLSegment().
    (gdb) clear sysAddDLSegment
    

  8. Set a break point in the shared library.
    (gdb) break HelloWorldImpl.c:Java_HelloWorld_greet
    

  9. Continue execution to the break point in the shared library.
    (gdb) continue
    

  10. Step through the native code.
    (gdb) step
    

    gdb has several other commands that display source code, show a stack trace and examine data. These are described in Debugging with GDB.