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.”
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.
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.
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
.
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:
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.
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.
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.
#define ENABLE_JAVA_DEBUGGER 0
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.
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.
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.
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.
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:
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.
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.
To start a debug session, the following five steps are necessary:
javac -g -classpath
<path> -d
<directory>
<class>
-g
indicates to include debug information-classpath
<path>, where <path> indicates the directory in which the CLDC/KVM Java library classes and the application classes for the application being debugged are located.-d
<directory>, where <directory> indicates the directory in which output classes will be written. The default output directory is ./output
.
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).
kvm -debugger -classpath
<path> -port
<KVM port>
<class>
-debugger
indicates to put the KVM in debugger enabled mode-classpath
<path>, where <path> specifies the directory in which the CLDC/KVM Java library classes as well as the application classes for the application being debugged are located.-port
<KVM port> is the KVM port. The default KVM port is 2800. This must match the KVM port specified by the debug agent below.
java -classpath
<path> kdp.KVMDebugProxy -l
<localport> -p -r
<KVM host> <KVM port> -cp
<KVM_path>
-classpath
<path>, where <path> specifies the directories in which the debug proxy classes are located.-l
<localport>, where <localport> is the port that the debugger connects to.-p
indicates to use the class parser.-r
<KVM host>, where <KVM host> is the remote host name.-cp
<path>, where <path> is the directory or directories where the CLDC/KVM Java library classes as well as the application classes for the application being debugged are located.
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.
For jdb (Java debugger), the command is as follows:
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:
tools/kdp/classes
, then the following command would invoke the debug agent (debug proxy). java -classpath . kdp.KVMDebugProxy -l 1234 -p -r sicily
2800 -cp ../../../api/classes:../../../samples/classes
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.
KVM Porting Guide , CLDC 1.1 |
Copyright © 2003 Sun Microsystems, Inc. All rights reserved.