Sunday, October 26, 2008

Debugging Java Code in the CVM

CVM supports the JVMDI, so you can connect a JPDA debugger to it without involving a
separate debug proxy of the type used by KVM. However, before you can start debugging,
you need to do two things:

1. Build the CVM with the CVM_DEBUG and CVM_JVMDI options to the make
command set to true.

2. Build the library libjdwp, which contains the native code that implements the Java
Debug Wire Protocol (JDWP). This protocol allows debuggers to connect to the VM
over a socket or using shared memory.

You can build libjdwp using the following commands:

cd $CDC/ext/jpda/build/linux
make

This creates the library and writes it to the file $CDC/jdk_build/linux/lib/i386/libjdwp.so.

Starting CVM for debugging requires quite a long command line:

cvm -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=5000
-Xbootclasspath:$CDC/build/linux/lib/foundation.jar -Dsun.boot.library.
path=$CDC/jdk_build/linux/lib/i386 -Djava.class.path=$EXAMPLES/src ora.
ch7.CVMProperties

The options used here are as follows:

-Xdebug

Starts the VM in debug mode. When this option is used, the VM suspends operation
before entering the main( ) method of the initial application class.

-Xrunjdwp

Tells the VM to use JDWP for debugging. This option requires several parameters,
separated from it by a colon and from each other by commas, that specify how the
debugger will connect to the VM. In this case, the parameters supplied are as follows:

transport=dt_socket
Specifies that the debugger will connect over a socket.

server=y
Tells JDWP to take on the server role. The debugger itself acts as the client.

address=5000
Specifies the port number that the server should use to listen for a connection from the
debugger client.

-Xbootclasspath

Specifies the location of VM's boot classes, in this case the set of core and Foundation
Profile classes that have not been prelinked into the VM.

-Dsun.boot.library.path

Gives the directory in which the JWDP implementation library (libjdwp.so for Linux)
can be found.

-Djava.class.path

Specifies the locations to be searched for application classes.

Once the CVM has started, it will suspend and wait for a debugger to connect to it on the port
specified in the -Xrunjdwp argument. If you have an IDE that supports remote JPDA
debugging (such as Forte for Java), you can use it to perform source- level debugging. Alternatively, you can use the command-line tool jdb, which is part of the J2SE SDK. To connect using jdb, you need to specify the socket address on which the VM is listening and the location at which the source code for the application's classes can be found:

jdb -attach localhost:5000 -sourcepath $EXAMPLES/src

Here, jdb and the CVM are assumed to be on the same machine, but this need not be the case.
Following connection, you need to use the step command to force the VM to enter the
main( ) method. From here, you can use jdb commands to set breakpoints, list the source
code around the line currently being executed, inspect and modify objects and fields, and so
on.

By default, when you use the -Xdebug argument, the JVMDI support in the VM starts at the
same time as the VM itself and suspends execution until a debugger connects to it. However,
you can choose to have JVMDI defer its initialization until an exception of a named type or an
uncaught exception is thrown. The latter case is often the type of error that you would like to
use a debugger to investigate, but starting the VM in debug usually causes it to execute byte
codes more slowly. Using this feature, you can run the VM at full speed until the exception
occurs.

The options that control the point at which the debugging features initialize is part of the -
Xrunjdwp argument. The following command:

cvm -Xdebug -Xrunjdwp:transport=dt_
socket,server=y,address=5000,onuncaught=y -Xbootclasspath:$CDC/build/
linux/lib/foundation.jar -Dsun.boot.library.path=$CDC/jdk_build/linux/
lib/i386 -Djava.class.path=$EXAMPLES/src ora.ch7.CVMException

adds the onuncaught option with value y, which delays initialization of the JVMDI until an
uncaught exception occurs. The class ora.ch7.CVMException used here waits for 10
seconds, then deliberately causes a NullPointerException that it does not catch, at which
point the debug facilities initialize and the VM is suspended. You can now start jdb to analyze
the problem.

Alternatively, if you simply want to start debugging when a NullPointerException occurs,
use the onthrow option, which requires the class name of the exception to wait for:

cvm -Xdebug -Xrunjdwp:transport=dt_
socket,server=y,address=5000,onthrow=java.lang.NullPointerException
-Xbootclasspath:$CDC/build/linux/lib/foundation.jar -Dsun.boot.library.
path=$CDC/jdk_build/linux/lib/i386 -Djava.class.path=$EXAMPLES/src
ora.ch7.CVMException


Finally, you can use the launch option to cause a command to be executed when the VM
debug facilities initialize. This option requires the name of the command, which should be
either an absolute path or the name of an executable on your search path (i.e., included in the
PATH variable for Linux). When it is started, the program receives the transport name and
address used by the debugger as arguments -- that is, it is effectively run with arguments like
this:

name dt_socket 5000

One way to make use of this is to create a script that starts jdb when the condition that starts
the JVMDI support occurs, like this:

#!/bin/sh
jdb -attach localhost:$2 -sourcepath $EXAMPLES/src

If you put these lines into a file called startdbg.sh (in a directory included in your PATH
variable) and make it executable (using a command like chmod +x startdbg.sh), the
following command:

cvm -Xdebug -Xrunjdwp:transport=dt_
socket,server=y,address=5000,onthrow=java.lang.
NullPointerException,launch=startdbg.sh -Xbootclasspath:$CDC/build/
linux/lib/foundation.jar -Dsun.boot.library.path=$CDC/jdk_build/linux/
lib/i386 -Djava.class.path=$EXAMPLES/src ora.ch7.CVMException

runs the VM until a NullPointerException occurs, at which point it initializes the JVMDI
code, suspends bytecode execution, and runs your script. This results in jdb starting and
connecting to the VM, using the port number that is passed as the second argument to the
script.

No comments: