Contents Previous Next

Chapter   16

Java-Level Debugging Support (KDWP)


KVM provides facilities for plugging the virtual machine into third-party Java development and debugging environments that are compliant with the JPDA (Java Platform Debug Architecture) specification supported by Java 2 Standard Edition. Further information on the JPDA architecture is available at
http://java.sun.com/products/jpda/.

Due to strict memory constraints, KVM does not implement support for the JVMDI (Java Virtual Machine Debug Interface) and full JDWP (Java Debug Wire Protocol) specifications required by JPDA.

Instead, KVM implements a subset of the JDWP known as KDWP (KVM Debug Wire Protocol). A specification of the KDWP protocol is available in a separate document listed in Section 1.2 “Related documentation.”

16.1 Overall architecture

The KDWP was designed to be a strict subset of the JDWP, primarily based on the resource constraints imposed on the KVM. In order to make KVM run with a JPDA-compatible debugger IDEs, a debug agent (debug proxy) program is interposed between the KVM and the JPDA-compatible debugger. The debug agent allows many of the memory-consuming components of a JPDA-compliant debugging environment to be located on the development workstation instead of the KVM, therefore reducing the memory overhead that the debugging interfaces have on the KVM and target devices. As obvious, the debugging interfaces can be turned off completely (at compile time) on those platforms and ports that do not need Java-level debugging support.

At the high level, the Java-level debugging support implementation consists of two parts:

The overall architecture for the Java-level debugging interface is illustrated in Figure 2. In that figure, the topmost box represents the JPDA-compliant debugging environment (“JPDA Debugger”) running on a development workstation. The debugger is connected to the debug agent that talks to the KVM.

Figure is completely described in previous paragraph.

FIGURE 2  –  Java-level debugging interface architecture

The debug agent connects to the KVM via a socket connection. Similarly, the debugger connects to the debug agent over a socket. The debugger is unaware that it is connected to the debug agent. The debugger appears to be communicating directly with a JDWP-compliant Java Virtual Machine. In fact, the debug agent can be configured in pass through mode so that all packets are passed from input to output using the debug agent with a standard Java VM. In normal KVM debug mode, the debug agent examines packets from the debugger and determines which packets are to be handled by the KVM and which are to be handled within the debug agent.

The main processing done in the debug agent is the parsing of class files to extract debugging information. This includes line number and code offset information and variable information. The KDWP implementation within the KVM includes some vendor specific commands that the debug agent uses to communicate with the KVM.

16.2 Debug Agent

The debug agent (also known as debug proxy) is written in the Java programming language and the code is in the KVM source tree under the directory tools/kdp/src/kdp. There are two main portions of the code: the portion that handles connections to the debugger and to KVM, and the portion that handles the parsing of the class files. The latter code is located in subdirectory classparser.

16.2.1 Connections between a debugger and the KVM

The portion of the code that handles connections to the debugger and to KVM resides in file KVMDebugProxy.java. This code creates two objects: DebuggerListener and KVMListener. The DebuggerListener class handles the retrieval of packets from the debugger, and the KVMListener class handles the retrieval of packets from the KVM. DebuggerListener and KVMListener are both subclasses of class Thread. Therefore, when they are invoked they start a new thread of execution (on the development workstation.) Each object also gets passed a handle to the other object (that is, the KVMListener object gets passed a handle to the DebuggerListener object, and vice versa). This enables cross-communication of packets between the debugger and the KVM. The following diagram (Figure 3) may help to clarify this further:

Figure is completely described in previous paragraph.

FIGURE 3  –  Debugger and KVM connections

In a typical scenario, the KVM is started with the -debugger flag, which puts it into a debugger enabled mode. In this mode the KVM listens on a socket for a connection from the debug agent. When the debug agent is started, it connects to this socket, and then listens on another socket for a connection from the debugger. When the debugger connects, it issues the JDWP handshake command, which consists of the string “JDWP-Handshake”. The debug agent acknowledges by reflecting this string back to the debugger. Meanwhile, the debug agent has sent the handshake command to the KVM and the KVM has responded back with information concerning which optional events it supports. The KVMListener then queries the KVM for a list of all the classes that are currently loaded into the VM. This information is used to build a hash table of ClassFile objects that is used later when the debugger requests information about a specific class (such as line number information, method information, and so forth.) At this point, each thread is listening for packets. The KVM sends a VMInit event to the debugger via the debug agent, which indicates to the debugger that the KVM is starting its execution of the Java application. The debugger might also send packets that indicate to the KVM to start up other events such as ClassPrepare or ClassLoad.

The communication code for the debug agent is in source file SocketConnection.java. In this file, each object (KVMListener and DebuggerListener) creates a thread of execution that waits for packets to arrive from its respective socket. If the packet is a command packet (the Packet.Reply bit is not set), then it puts that packet on a packetQueue list (see file ProxyListener.java) and a notification is sent to any object waiting on that queue. The packet is then extracted from the queue by whatever listener is waiting for that packet on that queue. In the run method for the KVMListener and DebuggerListener, each packet is analyzed to determine if the debug agent needs to process the packet or whether it is to be transmitted to the other object for further processing.

16.2.2 Packet processing

The DebuggerListener object intercepts a number of packets as is evident by examining the code for the large switch statement located after the call to waitForPacket. When waitForPacket returns with a packet, the debug agent first creates a new PacketStream object, then checks to see if the debug agent needs to process that packet (For example, the SENDVERSION_CMD packet is processed by the debug agent directly, and a response is created and sent back to the debugger without any interaction with the KVM.) A more complex command would be the FIELDS_CMD of the REFERENCE_TYPE_CMDSET. For this command, the debugger has passed in a class id, which is used by the debug agent to find a ClassFile object via the ClassManager.classMap object. The classMap object is filled by the KVMListener object when it receives the ClassPrepare events from the KVM. Once the debug agent has obtained the ClassFile object, it uses the getAllFieldInfo method to obtain a list of fields, and iterates through this list passing the information back to the debugger. Once again, there is no interaction with the KVM.

Similarly, within the source file for the KVMListener.java, the KVMListener object intercepts the CLASS_PREPARE events (events whose type is equal to the constant JDWP_EventKind_CLASS_PREPARE) that are passed up from the KVM. KVMListener creates a new ClassFile object via the call to manager.findClass and inserts it into the ClassManager.classMap hashtable. KVMListener then passes the event to the debugger so that it can process the event as well.

16.3 Debugger support within KVM

The debugger support within the KVM consists primarily of four source (.c) files under the VmExtra/src directory and three header (.h) files under VmExtra/h directory. All debugger code is included with the conditional compilation flag, ENABLE_JAVA_DEBUGGER. If this flag is enabled, and the KVM is rebuilt, then the Java debugger support is included within the KVM. If Java debugger support is not desired, set this define in main.h to 0.


Note – If your target platform or port does not require Java-level debugging support, we recommend turning the debugging code off at compile time (in file main.h or in your platform-specific machine_md.h file):

#define ENABLE_JAVA_DEBUGGER 0

This will make the KVM executable much smaller.

The primary file for the Java debugger support within the KVM is the source file debugger.c. This file contains all the support needed for the KDWP API. Socket communication is handled by the code in file debuggerSocketIO.c. The debuggerInputStream.c and debuggerOutputStream.c files contain the code for handling the transmission of data being sent to/from the debugger support functions in debugger.c. The code in debugger.c file services all the KDWP requests that are sent by or through the debug agent. The function ProcessDebugCmds handles the parsing of input packets to determine which command set and what command within the command set the packet is referring to. This function then determines the appropriate function that is to be invoked for handling this command. The inputStream handle as well as the outputStream handles are passed as parameters, and used for handling the reply back to the debug agent. For performance reasons, most commands use a global inputStream and outputStream. If these are already in use, another one is allocated from the heap.

16.3.1 Events

Events are essentially commands generated by the KVM. Events are passed up to the debug agent, which may in turn pass them up to the debugger. The code for handling an event will appear as follows:

#if ENABLE_JAVA_DEBUGGER 
{ 
  CEModPtr cep = GetCEModifier(); 
  cep->thread = thisThread; 
  setEvent_ThreadStart(cep); 
  FreeCEModifier(cep); 
} 
#endif /* ENABLE_JAVA_DEBUGGER */ 

This creates a new CEModPtr structure that contains state information for this particular event. It then invokes a routine in debugger.c, which attempts to send the event. A typical event routine in debugger.c first determines if the event attempting to be sent has been enabled by a previous Set Event command from the debugger (via the checkNOTIFY_WANTED macro.) Then, findSatisfyingEvent is invoked to determine if this particular event matches an event request sent down from the debugger. The findSatisfyingEvent function also checks the event counter as well as any modifiers that the debugger has applied to this event. If the event passes, then it is sent on the outputStream. After an event is sent, handleSuspendPolicy is invoked to process whatever suspend policy the debugger has attached to this event when the debugger had issued the Set Event command. Some events such as breakpoints or single stepping will generally have a suspend policy of ALL, which means that all threads are suspended and that the KVM will essentially spin through the reschedule loop at the top of the interpreter loop waiting for a thread to resume. The Resume command will eventually come from the debugger when the user issues a Continue command or when the user explicitly issues a Resume Thread command.

In certain situations, events need to be deferred. This is because it is not possible to send the event to the debug agent and subsequently suspend the KVM threads, since the interpreter might be in the midst of executing a byte code. Thus in such cases, insertDebugEvent(cep) is called instead of setEvent_XXX(cep), as shown in the example above. At the top of the interpreter loop (see VmCommon/src/execute.c), the events are checked, and if there is a pending event, it is sent when it is safe to do so.

16.3.2 Breakpoints

When a Set Event command is received to add a breakpoint, the code for handling the breakpoint event determines if the opcode at that particular location is a Fast opcode. If so, then the original opcode must be retrieved from the inline cache before the breakpoint is added. The original opcode is stored in an EVENTMODIFIER structure that is pointed to by the VMEvent structure for this particular event. When the Java bytecode interpreter hits the Breakpoint opcode (see bytecodes.c), and if not single stepping, then the handleBreakpoint function is invoked. This function restores the original opcode into the thread structure for the CurrentThread, at the point where the breakpoint had been entered. It then also sends an event to the debugger via the debug agent. Eventually, the user will press the Continue button on the debugger, which results in all threads to resume execution. The RESCHEDULE macro (see execute.h) includes some code in it for determining if this thread was just at a breakpoint, and if so, it will retrieve the next bytecode from a known location within the thread structure. The code within the interpreter loop will then execute this instruction.

16.3.3 Single stepping

When the debugger issues a SingleStep event request, the code in debugger.c must determine which type of step function it is (that is, step by bytecode or step by line), whether the step is a Step Into (step into a function), Step Over (step over calls to functions; that is, do not single step into another function), or Step Out (go back to the function that called this function). Additionally, if it is a step by line, then KVM needs to know what the code offset is for the next line number. To obtain this information, KVM calls a private API within the debug agent to return the target offset and the next line offset. The debug agent returns this information back to the KVM, which stores it into a stepInfo structure, which is part of the threadQueue structure (see thread.h.) Within the interpreter loop, a flag is checked to determine if this particular thread is in single step mode. If so, then the handleSingleStep function in debugger.c is invoked to process this single step. The handleSingleStep function determines if the instruction pointer has reached the target offset or if it has popped up a frame or if it has gone beyond the target offset. Depending on the type of stepping being performed, this function will determine when to send a SingleStep event to the debugger. In most cases, if the user is single stepping line by line, and when the code offset is equal to the target offset, it results in a SingleStep event to be sent to the debugger. All threads are typically suspended at this point, and as was the case for the breakpoint scenario above, the KVM will wait until the debugger resumes the threads via a Continue command or a subsequent SingleStep event.

16.3.4 Suspend and nosuspend options

It is desirable in certain IDE environments such as Borland’s JBuilder to provide an option similar to that available in J2SE for starting up the KVM in two different debugging modes. Thus, as of KVM 1.0.3, the KVM debugger can be started in the following two modes:

    kvm -debugger -suspend ... 
    kvm -debugger -nosuspend ... 

In the suspend mode (this is the default), the KVM stops all Java threads upon VM startup and waits for further commands from the IDE (development and debugging environment) before the debugging session proceeds any further. In the nosuspend mode, the Java threads start running immediately when the KVM is started.

In the most common cases, the KVM debugger is usually invoked in the suspend mode. Unless the application program being debugged requires substantial processing or is recursive in nature, it may not make much sense to invoke the KVM in the nosuspend mode. This is because it is very likely that a simple application program may complete execution long before the debugger IDE is able to issue any commands to the KVM.

16.4 Using the Debug Agent and the JPDA Debugger

In order to run the debug agent, it is necessary to build the application class or classes being debugged to include debug information. It is also necessary to transform the application class file(s) using the preverifier. Then, after the KVM is invoked on a specified host and port, the debug agent can be started so that it listens to KVM requests on the KVM port, and a local port is specified for connecting with a JPDA-compatible debugger.

The following section summarizes the steps necessary to start a debug session in much more detail.


Note – KVM debugger functionality is integrated into the J2ME Wireless Toolkit (WTK). Therefore, if you are using the WTK, the detailed steps in the next section are not necessary.

16.4.1 Starting a debug session

To start a debug session, the following five steps are necessary:

  1. Build the application classes to be debugged with the -g option to include debug information. Then, place the output in a separate directory for transforming the resulting class file. See Chapter 13, “Class File Verification.”
  2. javac -g -classpath <path> -d <directory> <class>

  3. Invoke the preverifier for transforming the class file.
  4. preverify -classpath <path> -d . <directory>

    This will transform all classes under <directory> and places the transformed class files in the current directory (as specified by the -d option).

  5. Start the KVM process:
  6. kvm -debugger -classpath <path> -port <KVM port> <class>

  7. Start the debug agent (debug proxy):
  8. java -classpath <path> kdp.KVMDebugProxy -l <localport> -p -r <KVM host> <KVM port> -cp <KVM_path>

  9. Connect to the debug agent with the debugger:

You can use any JPDA-compliant debugger such as Forte, JBuilder or jdb. With the Forte debugger, go to the Debug->Connect dialog box and insert the host where the debug agent is running and the local port number that had been specified using the -l <localport> option.


Note – To download the Forte debugger and for further information on Forte, please refer to the Sun One Studio website at
http://wwws.sun.com/software/sundev/jde/index.html. When running the Forte debugger, JDK 1.3 or later must be previously installed and be on the classpath, since only this version (or later) of the JDK includes support for the JPDA. For further information on downloading the JDK 1.3, please refer to the website at http://java.sun.com/j2se/1.3/.

For jdb (Java debugger), the command is as follows:

    jdb -attach <agent hostname>:<localport> 

16.4.2 Debugging example

If the KVM is running on a system called sicily, and the debug agent and debugger are running on debughost, then the commands for starting the debug session would appear as follows:

For example, in Forte, open the source file test.java and insert a breakpoint at some line in the file.) In Forte, go to the Debug->Connect menu selection. (Newer versions of Forte have an Attach menu item). Then, enter debughost in the hostname box, and enter 1234 in the port number box. Press OK.

At this point the Forte debugger communicates with the KVM, the program starts to run, and eventually it hits the breakpoint you inserted. You can now use Forte to single-step, read or set variable values, and so forth.

 


Contents Previous Next KVM Porting Guide
, CLDC 1.1