/*******************************************************************************
An implementation of the Java Debug Wire Protocol (JDWP) for JOP
Copyright (C) 2007 Paulo Abadie Guedes
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*******************************************************************************/
package debug.io;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import debug.JopDebugKernel;
import debug.constants.EventKindConstants;
import debug.constants.SuspendPolicyConstants;
/**
* This class provides a communication channel between the
* Java processor and the debug server.
*
* Its main role is to take care of packet handling,
* by isolating the inner details of creating, interpreting
* and transmiting JDWP packets.
*
* It has two internal buffers, for input and output.
* The input one is used to receive requests from the server
* and is needed to handle commands. It's intended to be used
* by an interrupt handler or a debug thread.
*
* The output buffer is used to build both command answers
* and events. It is intended to be cleared, filled in and
* then sent. This class also helps in the packaging.
*
* DebugKernelChannel.java
*
* @author Paulo Abadie Guedes
*
* 04/12/2007 - 16:02:52
*
*/
public final class DebugKernelChannel
{
// the streams used for debugging
private OutputStream outputStream;
private DataInputStream inputStream;
private JavaDebugPacket inputPacket;
private JavaDebugPacket outputPacket;
/**
* Create a debug channel for this Java machine.
* Using plain Input and Output streams allows it to work
* (at least in theory) over any reliable communication
* channel.
*
* @param input
* @param output
*/
public DebugKernelChannel(InputStream input, OutputStream output)
{
// initialize internal objects
if((input == null) || (output == null))
{
throw new NullPointerException("Debug channel not initialized: Null stream(s)!");
}
if(input instanceof DataInputStream)
{
inputStream = (DataInputStream) input;
}
else
{
inputStream = new DataInputStream(input);
}
outputStream = output;
inputPacket = new JavaDebugPacket();
outputPacket = new JavaDebugPacket();
}
/**
* Create and send a VMStart event packet, to notify that
* this Java machine started and can accept debug requests.
*
* @throws IOException
*/
public synchronized void sendVMStartEvent() throws IOException
{
createVMStartPacket();
sendPacket();
}
/**
* Create and send a VMDeath event packet, to notify that
* this Java machine will shut down.
*
* @throws IOException
*/
public synchronized void sendVMDeathEvent() throws IOException
{
createVMDeathPacket();
sendPacket();
}
/**
* Create and send an event packet, to notify that
* a breakpoint was reached.
*
* @param typeTag
* @param classId
* @param methodId
* @param methodLocation
* @throws IOException
*/
public synchronized void sendBreakpointEvent(int typeTag, int classId, int methodId,
int methodLocation) throws IOException
{
createBreakpointPacket(typeTag, classId, methodId, methodLocation);
sendPacket();
}
/**
* Create a VMStart event packet.
*/
private void createVMStartPacket()
{
createEventHeader();
writeEventKindAndRequestId(EventKindConstants.VM_START, 0);
writeDefaultThreadId();
}
/**
* Create a VMDeath event packet.
*/
private void createVMDeathPacket()
{
createEventHeader(1, SuspendPolicyConstants.NONE);
writeEventKindAndRequestId(EventKindConstants.VM_DEATH, 0);
}
/**
* Create a breakpoint event packet.
*
* @param typeTag
* @param classId
* @param methodId
* @param methodLocation
*/
private void createBreakpointPacket(int typeTag, int classId,
int methodId, int methodLocation)
{
createEventHeader(1, SuspendPolicyConstants.EVENT_THREAD);
writeEventKindAndRequestId(EventKindConstants.BREAKPOINT, 0);
writeThreadId(1);
writeExecutableLocation(typeTag, classId, methodId, methodLocation);
}
/**
* Write the default ThreadId (currently 1).
*/
private void writeDefaultThreadId()
{
writeThreadId(1);
}
/**
* Write the ThreadId.
*
* @param threadId
*/
private void writeThreadId(int threadId)
{
outputPacket.writeInt(threadId);
}
/**
* Write an executable location. The location is identified by:
*
* - one byte type tag
* - followed by a a classID
* - followed by a methodID
* - followed by an unsigned 8-byte index, which identifies
* the location within the method.
*
* The type tag is one byte long. Class and method ID's
* are 4 bytes long. The index is 8 bytes long.
*
* @param typeTag
* @param classId
* @param methodId
* @param methodLocation
*/
public void writeExecutableLocation(int typeTag, int classId,
int methodId, int methodLocation)
{
writeTypeTag(typeTag);
writeClassId(classId);
writeMethodId(methodId);
writeMethodLocation(methodLocation);
}
/**
*
* @param typeTag
*/
private void writeTypeTag(int typeTag)
{
outputPacket.writeByte(typeTag);
}
private void writeClassId(int classId)
{
//outputPacket.writeInt(0);
outputPacket.writeInt(classId);
}
private void writeFieldId(int fieldId)
{
outputPacket.writeInt(fieldId);
}
private void writeMethodId(int methodId)
{
outputPacket.writeInt(methodId);
}
private void writeObjectId(int objectId)
{
outputPacket.writeInt(objectId);
}
private void writeFrameId(int frameId)
{
outputPacket.writeInt(frameId);
}
private void writeMethodLocation(int location)
{
outputPacket.writeInt(0);
outputPacket.writeInt(location);
}
/**
* Write the EventKind and RequestId fields. Useful to avoid issues
* with field sizes.
*
* @param eventKind
* @param requestId
*/
private void writeEventKindAndRequestId(int eventKind, int requestId)
{
outputPacket.writeByte(eventKind);
outputPacket.writeInt(requestId);
}
/**
* Create an event header with one event.
*/
private void createEventHeader()
{
createEventHeader(1, SuspendPolicyConstants.ALL);
}
/**
* Create an event header.
*
* @param events
*/
private void createEventHeader(int events, int suspendPolicy)
{
// discard previous data and create a new event header
outputPacket.createEventHeader();
// write the suspend policy.
outputPacket.writeByte(suspendPolicy);
// write the number of events.
outputPacket.writeInt(events);
}
/**
* Send the output packet to the debug server.
*
* @throws IOException
*/
private synchronized void sendPacket() throws IOException
{
outputPacket.writePacket(outputStream);
JopDebugKernel.debugPrint("Packet sent! ");
if(JopDebugKernel.shouldPrintInternalMessages())
{
printOutputPacketDescription();
}
}
/**
* This method should be called when there's something to be read
* from the external environment.
*
* It should be called from somewhere else which will
* handle a communication interrupt related to the
* corresponding input stream.
*
* @throws IOException
*/
public synchronized void receivePacket() throws IOException
{
inputPacket.readPacket(inputStream);
JopDebugKernel.debugPrint("Packet arrived!");
if(JopDebugKernel.shouldPrintInternalMessages())
{
printInputPacketDescription();
}
}
/**
* Create an empty reply packet, based on the content of the last
* received packet. It will report a successful result.
*
* @throws IOException
*/
private synchronized void createEmptyReplyPacket() throws IOException
{
createEmptyReplyPacket(0);
}
/**
* Create an empty reply packet, based on the content of the last
* received packet. It can report an error or a successful result.
*
* @throws IOException
*/
private synchronized void createEmptyReplyPacket(int errorCode) throws IOException
{
outputPacket.createReplyHeader(inputPacket);
outputPacket.setErrorCode(errorCode);
}
/**
* Create a reply packet based on the last received packet.
* Add the given int as internal data.
*
* @param value
* @throws IOException
*/
private synchronized void createIntReplyPacket(int value) throws IOException
{
// create a reply packet based on the received packet.
// the content will be the given int.
createEmptyReplyPacket();
outputPacket.writeInt(value);
}
/**
* Send a reply packet based on the last received packet.
* Insert no data. Usually it's used just as an acknowledge,
* to report that the last request was correctly executed.
*
* @param frameCount
* @throws IOException
*/
public synchronized void sendReply() throws IOException
{
// create a reply packet based on the received packet.
createEmptyReplyPacket();
sendPacket();
}
/**
* Send a reply packet based on the last received packet.
* Insert the frame count in the data.
*
* @param frameCount
* @throws IOException
*/
public synchronized void sendReplyFrameCount(int frameCount) throws IOException
{
// create a reply packet based on the received packet.
// the content will be the frame count.
createIntReplyPacket(frameCount);
sendPacket();
}
/**
* Send a reply packet based on the last received packet.
* Insert the local variable content in the data.
*
* @param value
* @throws IOException
*/
public synchronized void sendReplyGetLocalVariable(int value) throws IOException
{
// the content will be the local variable value.
// currently there's support to provide only one local variable.
createIntReplyPacket(1);
outputPacket.writeInt(value);
sendPacket();
}
/**
* Send a reply packet based on the last received packet.
* Insert the number of local variables.
*
* @param localVariableCount
* @throws IOException
*/
public synchronized void sendReplyLocalVariableCount(int localVariableCount) throws IOException
{
// create a reply packet based on the received packet.
// the content will be the number of local variables.
createIntReplyPacket(localVariableCount);
sendPacket();
}
/**
* Send a reply packet based on the last received packet.
* Insert the overwritten instruction.
*
* @param instruction
* @throws IOException
*/
public synchronized void sendReplySetBreakpoint(int instruction) throws IOException
{
// create a reply packet based on the received packet.
// the content will be the overwritten instruction
createIntReplyPacket(instruction);
sendPacket();
}
public synchronized void sendReplyTestJDWPPackets(int numPackets) throws IOException
{
// create a reply packet based on the received packet.
// the content will be the number of packets that will follow.
createIntReplyPacket(numPackets);
sendPacket();
}
/**
* Send a reply packet with an error code, based on the last received packet.
* The error code will be sent in the "error" field, to inform the error to
* the server.
*
* @param frameCount
* @throws IOException
*/
public synchronized void sendReplyWithErrorCode(int errorCode) throws IOException
{
// create and send a reply packet based on the last received packet.
createEmptyReplyPacket(errorCode);
sendPacket();
}
/**
* Provide access to the "command set" field of the input packet.
*
* @return
* @throws IOException
*/
public synchronized int readInputCommandSet() throws IOException
{
return inputPacket.getCommandSet();
}
/**
* Provide access to the "command" field of the input packet.
*
* @return
* @throws IOException
*/
public synchronized int readInputCommand() throws IOException
{
return inputPacket.getCommand();
}
/**
* Read one int value from the input packet.
*
* @return
*/
public synchronized int readIntValue()
{
return inputPacket.readInt();
}
/**
* Read one short value from the input packet.
*
* @return
*/
public synchronized int readShortValue()
{
return inputPacket.readShort();
}
/**
* Read one byte value from the input packet.
*
* @return
*/
public synchronized int readByteValue()
{
return inputPacket.readByte();
}
/**
* Skip some bytes from the input packet.
*
* @param numBytes
*/
public synchronized void skipBytes(int numBytes)
{
inputPacket.skipBytes(numBytes);
}
/**
* Skip four bytes from the input packet.
*
* @param numBytes
*/
public synchronized void skipInt()
{
skipBytes(4);
}
/**
* Print information about the input packet.
*/
private void printInputPacketDescription()
{
inputPacket.printPacketHeader(System.out);
}
private void printOutputPacketDescription()
{
outputPacket.printPacketHeader(System.out);
}
// Unfortunately there's no simple way to do this with one method call
// (and without using dynamic lists), because the stack size changes
// during execution.
// So, the debug kernel uses the three methods below.
// It does this for every "getstack frame list" command:
//
// - call prepareStackFrameListPacket once
// - call many times writeStackFrameInformation
// - call sendStackFrameListReply once
//
/**
* Prepare the reply for the "get stack frame list" command.
*
* @param count
* @throws IOException
*/
public void prepareStackFrameListPacket(int count) throws IOException
{
// create a reply packet based on the received packet.
createEmptyReplyPacket();
// write the number of stack frames.
outputPacket.writeInt(count);
}
/**
* Write information related to one stack frame to the output packet.
*
* @param framePointer
*/
public synchronized void writeStackFrameId(int framePointer)
{
outputPacket.writeInt(framePointer);
}
/**
* Send the packet with data about stack frames to the server.
*
* @throws IOException
*/
public synchronized void sendStackFrameListReply() throws IOException
{
sendPacket();
}
/**
* Prepare the reply for the "get instance values" command.
*
* @param count
* @throws IOException
*/
public void prepareInstanceValuesPacket(int count) throws IOException
{
// create a reply packet based on the received packet.
createEmptyReplyPacket();
// write the number of instance field values.
outputPacket.writeInt(count);
}
/**
* Write information related to one instance field on the output packet.
*
* @param framePointer
*/
public synchronized void writeInstanceFieldValue(int fieldValue)
{
outputPacket.writeInt(fieldValue);
}
/**
* Send the packet with data about instance field values to the server.
*
* @throws IOException
*/
public synchronized void sendInstanceFieldValuesReply() throws IOException
{
sendPacket();
}
}