/******************************************************************************* 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; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import debug.constants.TagConstants; import com.jopdesign.sys.Const; import com.jopdesign.sys.GC; import com.jopdesign.sys.Native; import debug.constants.CommandConstants; import debug.constants.ErrorConstants; import debug.constants.JOPConstants; import debug.io.DebugKernelChannel; import debug.io.EmbeddedOutputStream; /** * JopDebugKernel.java * * This class is responsible to provide all debug services needed * inside the JOP machine. * * @author Paulo Abadie Guedes * * 03/06/2007 - 12:00:48 * */ public final class JopDebugKernel { private static DataInputStream inputStream; private static DataOutputStream outputStream; private static int mainMethodFramePointer = 0; // private static final int STACK_BASE_POINTER = RtThreadImpl.MAX_STACK + 5; // private static final int STACK_BASE_POINTER = RtThreadImpl.MAX_STACK + 6; // private static final int STACK_BASE_POINTER = Const.STACK_SIZE + 6; private static final int STACK_BASE_POINTER = Const.STACK_OFF + 6; private static final int MASK_FIRST_BYTE = 0x00FFFFFF; private static final int MASK_SECOND_BYTE = 0xFF00FFFF; private static final int MASK_THIRD_BYTE = 0xFFFF00FF; private static final int MASK_FOURTH_BYTE = 0xFFFFFF00; private static final int INVALID_INSTRUCTION = -1; private static final int NOP_INSTRUCTION = 0x00; private static final int BREAKPOINT_INSTRUCTION = 0x00CA; // currently the maximum number of local variables is 32. private static final int MAX_LOCAL_VARIABLES = 32; private static boolean initialized = false; private static int breakpointMethodPointer = 0; private static DebugKernelChannel debugChannel; // a variable to hold the frame pointer of the breakpoint method private static int breakpointFramePointer; // an internal flag to inform error codes during object access. // used to avoid memory allocation when creating exceptions. private static int errorCodeForInstanceFieldAccess; // internal flag to turn on/off tracing messages //private static boolean shouldPrintInternalMessages = false; private static boolean shouldPrintInternalMessages = true; // the code below does not work. Why? // static // { // inputStream = new DataInputStream(System.in); // outputStream = System.out; // } /** * This is the main method for this class. It is resposible * to answer debug requests from the desktop and can be used * to interactively manipulate the stack. * * The services implemented here does NOT correspond exactly * to those designed by the JDWP specification. However, they are * at least the basic blocks with which those services can be * fully implemented on the server side. * * Currently it is possible to: * * 1) Exit the method * 2) Invoke a static method given its address and one parameter * 3) Calculate the stack depth of the caller method * 4) Get a local variable value * 5) Set a local variable value * * Todo: * 6) Request a stack frame * * @throws IOException * */ public static final void breakpoint() { if(initialized == false) { initialize(); } // for tracing. enableDevelopmentInternalMessages(); // update the frame pointer for the breakpoint method. breakpointFramePointer = getCurrentFramePointer(); // notify the debug server that a breakpoint was reached. // Use the current frame pointer because this is the frame // which holds data related to the previous method call. // int framePointer = getCurrentFramePointer(); // sendBreakpointEvent(framePointer); int commandset, command; debugPrint("Breakpoint! Current stack depth: "); debugPrintln(getStackDepth()); commandset = 0; while(commandset >= 0) { TestJopDebugKernel.printLine(); try { // receive the next JDWP packet debugChannel.receivePacket(); // get the "command set" and "command" fields commandset = debugChannel.readInputCommandSet(); command = debugChannel.readInputCommand(); // commandset = inputStream.read(); // command = inputStream.read(); debugPrint("CommandSet:"); debugPrint(commandset); debugPrint(" Command:"); debugPrintln(command); // exit from this method. if((commandset == CommandConstants.VirtualMachine_Command_Set) && (command == CommandConstants.VirtualMachine_Exit)) { debugPrintln("Exit: stop execution."); handleExitCommmand(); break; } // // instead of returning the value of one static field, now is used to // // read the value of one position of memory. // if((commandset == 2) && (command == 6)) // { //// under development - should not compile now. //// // int value = 0; // // // get start address // int address = inputStream.readInt(); // // // get number of words to read // int size = inputStream.readInt(); // // while(size > 0) // { // value = Native.rdMem(address); // writeInt(value); // size --; // address ++; // } // // // get field index. May be the start address of a reference or not. //// int fieldIndex = inputStream.read(); //// //// // the number of bytes to read //// int fieldSize = inputStream.read(); //// //// int addr = Native.rdMem(cp+idx); //// stack[++sp] = Native.rdMem(addr); //// //// ////// // get field size ////// int fieldSize = inputStream.read(); ////// ////// if(fieldSize == 1) ////// { ////// int ////// } // // continue; // } // invoke a static method with one integer parameter on the stack if((commandset == CommandConstants.ClassType_Command_Set) && (command == CommandConstants.ClassType_InvokeMethod)) { debugPrintln("Invoke static"); handleInvokeStaticCommand(); continue; } // get a field from an object if((commandset == CommandConstants.ObjectReference_Command_Set) && (command == CommandConstants.ObjectReference_GetValues)) { debugPrintln("GetValues (2)"); handleGetInstanceVariable(); continue; } // set a field into an object if((commandset == CommandConstants.ObjectReference_Command_Set) && (command == CommandConstants.ObjectReference_SetValues)) { debugPrintln("SetValues (3)"); handleSetInstanceVariable(); continue; } // resume execution. Finish this method and continue. if((commandset == CommandConstants.ThreadReference_Command_Set) && (command == CommandConstants.ThreadReference_Resume)) { debugPrintln("Resume execution"); handleResumeExecutionCommand(); // stop the loop break; } // return a list of all stack frame locations if((commandset == CommandConstants.ThreadReference_Command_Set) && (command == CommandConstants.ThreadReference_Frames)) { debugPrintln("Get stack frames"); handleGetStackFramesCommand(breakpointFramePointer); continue; } // calculate the stack depth of the caller method. if((commandset == CommandConstants.ThreadReference_Command_Set) && (command == CommandConstants.ThreadReference_FrameCount)) { debugPrintln("Get stack depth"); handleGetStackDepthCommand(breakpointFramePointer); continue; } // dump the call stack. For development ONLY. // NOT a standard JDWP command set/command pair. if((commandset == CommandConstants.ThreadReference_Command_Set) && (command == 13)) { debugPrintln("Print the call stack."); handlePrintCallStackCommand(); continue; } // dump one stack frame. For development ONLY. // NOT a standard JDWP command set/command pair. if((commandset == CommandConstants.ThreadReference_Command_Set) && (command == 14)) { debugPrintln("Print a stack frame."); handlePrintStackFrameCommand(); continue; } // ---------------------- // breakpoint commands // ---------------------- // set breakpoint if((commandset == CommandConstants.EventRequest_Command_Set) && (command == CommandConstants.EventRequest_Set)) { debugPrintln("set breakpoint"); handleSetBreakPointCommand(); continue; } // clear breakpoint if((commandset == CommandConstants.EventRequest_Command_Set) && (command == CommandConstants.EventRequest_Clear)) { debugPrintln("clear breakpoint"); handleClearBreakPointCommand(); continue; } // get the method pointer from a stack frame // TODO: STILL NEED TO BE FIXED if((commandset == CommandConstants.StackFrame_Command_Set) && (command == 0)) { debugPrintln("Get method pointer"); handleGetStackFrameMPCommand(); continue; } // get a local variable value if((commandset == CommandConstants.StackFrame_Command_Set) && (command == CommandConstants.StackFrame_GetValues)) { debugPrintln("Get local variable"); handleGetLocalVariableCommand(); continue; } // set a local variable value if((commandset == CommandConstants.StackFrame_Command_Set) && (command == CommandConstants.StackFrame_SetValues)) { debugPrintln("Set local variable"); handleSetLocalVariableCommand(); continue; } // return the nuber of local variables if((commandset == CommandConstants.StackFrame_Command_Set) && (command == 5)) { debugPrintln("Get number of local variables"); handleGetNumberOfLocalVariablesCommand(); continue; } // specific command just to help development. if((commandset == CommandConstants.Event_Composite) && (command == 1)) { debugPrintln("Debug development command: Send JDWP packets"); handleTestSendJDWPPackets(); continue; } // specific command just to help development. // TODO: STILL NEED TO BE FIXED if((commandset == CommandConstants.Event_Composite) && (command == 2)) { debugPrintln("Debug development command: receive JDWP packets"); handleTestReceiveJDWPPackets(breakpointFramePointer); continue; } debugPrintln("Received invalid command or command set! "); debugPrint("Command: "); debugPrint(command); debugPrint(" Command set: "); debugPrint(commandset); debugPrintln(); } catch(IOException exception) { debugPrintln("Failure: " + exception.getMessage()); exception.printStackTrace(); break; } } debugPrintln("Returning from \"breakpoint\"."); } /** * Send a breakpoint event to the debug server. * * By using the frame pointer as unique reference, it's * possible to send arbitrary breakpoint events related * to any frames present in the call stack. That's good. */ private static void sendBreakpointEvent(int framePointer) { int typeTag, classId, methodId, methodLocation; // calculate all fields needed to be sent through the channel typeTag = getTypeTag(framePointer); classId = getClassReferenceFromFrame(framePointer); methodId = getMPFromFrame(framePointer); methodLocation = getPCFromFrame(framePointer); // request the channel to send a message to the debug server. try { debugChannel.sendBreakpointEvent(typeTag, classId, methodId, methodLocation); } catch (IOException exception) { System.out.println("Failure: " + exception.getMessage()); exception.printStackTrace(); } } /** * @param framePointer * @return */ private static int getTypeTag(int framePointer) { // for now, just return 1 (CLASS) return 1; } /** * @param framePointer * @return */ private static int getClassReferenceFromFrame(int framePointer) { int cp; // get the pointer to the constant pool cp = getCPFromFrame(framePointer); //go back one word. Now it points to the class reference! cp --; // return a pointer to the class structure. return Native.rdMem(cp); } /** * @param breakpointFramePointer * @throws IOException */ private static void handleGetStackDepthCommand(int breakpointFramePointer) throws IOException { // get stack depth of the breakpoint method int count = getStackDepth(breakpointFramePointer); debugPrint(" Stack depth of caller method: "); debugPrintln(count); debugChannel.sendReplyFrameCount(count); } /** * @throws IOException */ private static void handlePrintCallStackCommand() throws IOException { prettyPrintStack(); // just for development //TestJopDebugKernel.dumpCallStack(); debugChannel.sendReply(); } private static void handlePrintStackFrameCommand() throws IOException { // get stack frame index to be printed (this frame) int frameIndex; int previousFrameIndex; int framePointer,previousFramePointer; // debugPrintln("Starting handlePrintStackFrameCommand"); debugChannel.skipInt(); frameIndex = debugChannel.readIntValue(); previousFrameIndex = frameIndex + 1; // debugPrint("Frame index to print: "); // debugPrintln(frameIndex); if(previousFrameIndex < getStackDepth()) { previousFramePointer = getFramePointerAtIndex(previousFrameIndex); framePointer = getNextFramePointer(previousFramePointer); prettyPrintStackFrame(framePointer, previousFramePointer); debugChannel.sendReply(); } else { debugPrintln("Failure: invalid index -> " + frameIndex); debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_FRAMEID); } } /** * Handle the "Frames" command. * The current implementation of this method always answer * with the entire stack: it just ignore partial requests * (and the internal fields). * * @throws IOException */ private static void handleGetStackFramesCommand(int breakpointFramePointer) throws IOException { debugPrintln(" Will read startFrame"); int startFrame; // int numFrames; // for now, ignore the thread ID and startFrame debugChannel.skipInt(); startFrame = debugChannel.readIntValue(); // debugChannel.skipInt(); debugPrint(" startFrame: "); debugPrintln(startFrame); //numFrames = debugChannel.readIntValue(); // for now, ignore the number of frames. Consider always "all frames". debugChannel.skipInt(); int framePointer, count; // get the (current) maximum value for the stack depth count = getStackDepth(breakpointFramePointer); // index out of valid bounds. Ignore request, send error message and return. if(startFrame < 0 || startFrame >= count) { debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_THREAD); return; } // now we know it's a valid start frame index. // get the first frame pointer and send all frames to the server //framePointer = getFramePointerAtIndex(startFrame); // just to test. The line below will expose also the frame for breakpoint. //framePointer = getCurrentFramePointer(); framePointer = breakpointFramePointer; count = getStackDepth(framePointer); debugPrint(" Stack depth:"); debugPrintln(count); debugChannel.prepareStackFrameListPacket(count); boolean shouldContinue = true; while(shouldContinue) { debugPrintln(" Will add one frame."); // all registers inside the frame are related to the previous frame // context. So, send data about one frame based on the // frame pointer right above it. // write the pointer for the next frame debugChannel.writeStackFrameId(getNextFramePointer(framePointer)); // write the register values which are inside this frame writeLocation(framePointer); // walk to the next stack position framePointer = getNextFramePointer(framePointer); if(isFirstFrame(framePointer)) { shouldContinue = false; } // just for development. //prettyPrintStack(); } // and finally send the reply packet with all data inside. debugChannel.sendStackFrameListReply(); // debugPrintln(" Sent frame."); // debugPrintln(); debugPrintln("Done! "); } private static void writeLocation(int framePointer) { int typeTag, classId, methodId, methodLocation; // calculate all fields needed to be sent through the channel typeTag = getTypeTag(framePointer); classId = getClassReferenceFromFrame(framePointer); methodId = getMPFromFrame(framePointer); methodLocation = getPCFromFrame(framePointer); debugChannel.writeExecutableLocation(typeTag, classId, methodId, methodLocation); } /** * Handle the "Resume execution" command. * * @throws IOException */ private static void handleResumeExecutionCommand() throws IOException { // acknowledge the command and resume execution. debugChannel.sendReply(); debugPrintln(" Received \"Resume (11, 3)\" command."); //TODO: now how can I run the bytecode that was standing where this // breakpoint is, now? // just for development,dump the call stack. TestJopDebugKernel.dumpCallStack(); } /** * Handle the "Invoke static" command. * * @throws IOException */ private static void handleInvokeStaticCommand() throws IOException { int methodId; int argument; methodId = debugChannel.readIntValue(); argument = debugChannel.readIntValue(); debugPrint(" Method ID: " ); debugPrint(methodId); debugPrint(" Argument: " ); debugPrintln(argument); if(breakpointMethodPointer == methodId) { debugPrintln("Invalid method pointer!"); debugPrintln("Cannot call breakpoint from itself."); } else { int sp = Native.getSP(); debugPrint("SP = "); debugPrintln(sp); // sp++; // Native.wrIntMem(argument, sp); // Native.setSP(sp); // test just to see if it breaks. It doesn't if SP is restored later. // Native.setSP(sp + 4); debugPrintln("Calling method now:"); Native.invoke(argument, methodId); // now restore SP to its previous value. Doing this without // getting the top value just ignores anything left on the stack. // sp = sp - 1; Native.setSP(sp); // debugPrintln("Right after return."); // sp = Native.getSP(); // debugPrint("SP = "); // debugPrintln(sp); } // send a reply packet to report that this command finished successfully. debugChannel.sendReply(); } /** * Handle the "Exit" command. * * @throws IOException */ private static void handleExitCommmand() throws IOException { int exitCode; exitCode = debugChannel.readIntValue(); // acknowledge the command and exit. debugChannel.sendReply(); // TODO: where is the proper place to send a VMDeath event? // maybe after the main method return, too? ;) debugChannel.sendVMDeathEvent(); debugPrint(" Received \"Exit (10)\" command. Code: "); debugPrintln(exitCode); debugPrintln(" Shutting down... "); System.exit(exitCode); } /** * Write one int value to the output stream. * * @param value * @throws IOException */ private static void writeInt(int value) throws IOException { // TODO: remove this method in the future. System.out.println("WARNING!!! don't use this method directly!"); outputStream.writeInt(value); } /** * Set the default debug streams. * * // WARNING! this method should not be called outside breakpoint(); * method. */ private static void initialize() { breakpointMethodPointer = getMPFromFrame(getCurrentFramePointer()); EmbeddedOutputStream embeddedStream = new EmbeddedOutputStream(System.out); setDebugStreams(System.in, embeddedStream); // initialize the debug channel debugChannel = new DebugKernelChannel(inputStream, outputStream); // send a VM Start event // try // { // // TODO uncomment to send this event! // debugChannel.sendVMStartEvent(); // } // catch (IOException exception) // { // debugPrintln("Failure: " + exception.getMessage()); // exception.printStackTrace(); // } initialized = true; } /** * This method can be used in the future, to set the streams which will be * used to communicate with the JOP machine. * * @param in * @param out */ private static void setDebugStreams(InputStream in, OutputStream out) { if(in instanceof DataInputStream) { inputStream = (DataInputStream) in; } else { inputStream = new DataInputStream(in); } if(out instanceof DataOutputStream) { outputStream = (DataOutputStream) out; } else { outputStream = new DataOutputStream(out); } } private static int getCPLocalsArgsFromMP(int mp) { return Native.rdMem(mp + 1); // cp, locals, args } private static int getArgCountFromVal(int val) { return val & 0x1f; } private static int getLocalsCountFromVal(int val) { return ((val >>> 5) & 0x1f); } private static int getCPFromMP(int mp) { int value = getCPLocalsArgsFromMP(mp); value = value >>> 10; return value; // cp } private static int getMPFromFrame(int frame) { return Native.rdIntMem(frame + 4); } private static int getCPFromFrame(int frame) { return Native.rdIntMem(frame + 3); } private static int getVPFromFrame(int frame) { return Native.rdIntMem(frame + 2); } private static int getPCFromFrame(int frame) { return Native.rdIntMem(frame + 1); } private static int getSPFromFrame(int frame) { return Native.rdIntMem(frame); } /** * Return the frame pointer on the previous stack frame, * based on the given frame pointer. * * @param framePointer the pointer to the frame under inspection * @return */ public static final int getNextFramePointer(int framePointer) { int vp, args, loc; int mp, val; // debugPrint("getNextFramePointer(int frame)"); // debugPrintln(framePointer); if(isFirstFrame(framePointer)) { System.err.println(" Error! called getNextFramePointer with first pointer."); return 0; } else { mp = getMPFromFrame(framePointer); val= getCPLocalsArgsFromMP(mp); args = getArgCountFromVal(val); loc = getLocalsCountFromVal(val); vp = getVPFromFrame(framePointer); return vp + args + loc; } } /** * Calculate the number of local variables based on the * frame pointer to a call stack frame. * It consider the "this" reference, parameters and locals. * * @param frame * @return */ private static int getNumLocalsAndParametersFromFrame(int frame) { int loc; int localsPointer = getLocalsPointerFromFrame(frame); loc = frame - localsPointer; // debugPrint("Frame: "); // debugPrint(frame); // debugPrint(" localsPointer: "); // debugPrint(localsPointer); // // debugPrint(" loc: "); // debugPrint(loc); // if(isFirstFrame(frame)) // { // debugPrintln("First frame!"); // } return loc; } /** * Calculate the pointer to local variables based on the frame * pointer and the stack pointer of the previous frame. * * This assume that the previous stack pointer will always be * pointing to one position before the local variables * of the given frame. * * @param frame * @return */ private static int getLocalsPointerFromFrame(int frame) { int previous_sp = getSPFromFrame(frame); int localsPointer = previous_sp + 1; return localsPointer; } public static final int getLocalVariable(int frame, int fieldIndex) { int numLoc; int vp; int value = 0; numLoc = getNumLocalsAndParametersFromFrame(frame); // debugPrintln(" getField(int frame, int fieldIndex) frame = " + frame); // debugPrintln("Num. Locals: " + numLoc); if(fieldIndex < numLoc && fieldIndex >= 0) { // vp = getVPFromFrame(frame); vp = getLocalsPointerFromFrame(frame); // debugPrintln(" Calculating VP: " + vp); // value = Native.rdMem(vp + fieldIndex); value = Native.rdIntMem(vp + fieldIndex); // debugPrintln(" Value read from stack frame is: " + value); // value ++; // Native.wrMem(value, vp + fieldIndex); } else { debugPrintln(" Invalid index: " + fieldIndex); debugPrintln(" Num. locals is: " + numLoc); } return value; } /** * * @param frame * @param fieldIndex * @param value */ public static final void setLocalVariable(int frame, int fieldIndex, int value) { int numLoc; int vp; // get MP // get method structure // get number of local variables // check if the index is valid for this method // if so: // get previous CP // calculate variable location (location = CP + index) // get variable at location // print // increment, set numLoc = getNumLocalsAndParametersFromFrame(frame); // debugPrintln(" setField(int frame, int fieldIndex, value) frame = " + frame); // debugPrintln("Num. Locals: " + numLoc); if(fieldIndex < numLoc && fieldIndex >= 0) { // vp = getVPFromFrame(frame); vp = getLocalsPointerFromFrame(frame); // debugPrintln(" Calculating VP: " + vp); // value = Native.rdMem(vp + fieldIndex); Native.wrIntMem(value, vp + fieldIndex); // debugPrintln(" Value written to stack frame is: " + value); } else { debugPrintln(" Invalid index: " + fieldIndex); debugPrintln(" Num. locals is: " + numLoc); } } /** * Return the frame pointer of the caller's method. * * @return */ public static final int getCurrentFramePointer() { int frame; frame = Native.getSP() - 4; return getNextFramePointer(frame); } public static final int getCurrentVP() { int frame = getCurrentFramePointer(); frame = getNextFramePointer(frame); return getVPFromFrame(frame); } public static final int getFramePointerOfCallerMethod() { int frame; // get pointer of this method and previous ones frame = getCurrentFramePointer(); frame = getNextFramePointer(frame); frame = getNextFramePointer(frame); return frame; } /** * Check if the given frame is the first one on the call stack. * * Ignore some internal structures used to call the "main" * method. Consider the call stack frame for "main" * as the first one in the stack. */ public static final boolean isFirstFrame(int framePointer) { // assume it's not the first and try to show otherwise boolean result = false; // debugPrintln("isFirstFrame()"); if(mainMethodFramePointer == 0) { //called only once to calculate the main method pointer. mainMethodFramePointer = calculateMainMethodFramePointer(); // debugPrint("main method calculated: "); // debugPrintln(mainMethodFramePointer); } if(framePointer <= mainMethodFramePointer) { // this happens only on the "main" call result = true; } // if(framePointer > (RtThreadImpl.MAX_STACK + 5)) // { // framePointer = getNextFramePointer(framePointer); // if(framePointer <= (RtThreadImpl.MAX_STACK + 5)) // { // // this happens only on the "main" call // result = true; // } // } return result; } /** * Method to calculate the stack frame pointer of the 'main' method. * * @return */ private static int calculateMainMethodFramePointer() { // the pointer to "main" is at position (6 + numLocals). Anything greater // than this is not pointing to the first frame. // the first 5 bytes are the stack frame for the "boot" call to main. // The next bytes are the argument plus local variables. // Then comes the location of the first frame pointer. int var = Native.rdMem(1); // pointer to 'special' pointers debugPrintln("Pointer to 'special' pointers:" + var); var = Native.rdMem(var+3); // pointer to main method struct debugPrintln("Pointer to main method struct:" + var); var= getCPLocalsArgsFromMP(var); // get val from 'main' method structure debugPrintln("Main arguments:" + getArgCountFromVal(var)); debugPrintln("Main locals:" + getLocalsCountFromVal(var)); // get the number of local variables in the stack frame var = getArgCountFromVal(var) + getLocalsCountFromVal(var); debugPrintln("Stack base pointer: " + STACK_BASE_POINTER); // return the main method pointer using the stack base pointer as reference return (STACK_BASE_POINTER + var); } /** * Calculate how many frames are before the given one. * The depth of the first frame (for the main method) is zero. * Methods called inside "main" will have depth = 1 * and so on. * * @param framePointer * @return */ public static final int getStackDepth(int framePointer) { int count = 0; while(isFirstFrame(framePointer) == false) { count++; framePointer = getNextFramePointer(framePointer); } return count; } /** * Return the current stack depth. * * @return */ private static int getStackDepth() { int framePointer = getFramePointerOfCallerMethod(); return getStackDepth(framePointer); } private static int getInstanceSizeFromClass(int classReference) { return Native.rdMem(classReference + JOPConstants.CLASS_OFFSET_INSTANCE_SIZE); } private static int getPointerToStaticPrimitiveFields(int classReference) { return Native.rdMem(classReference + JOPConstants.CLASS_OFFSET_STATIC_PRIMITIVE_FIELDS); } private static int getGCInfo(int classReference) { return Native.rdMem(classReference + JOPConstants.CLASS_OFFSET_GC_INFO); } private static int getPointerToSuperclass(int classReference) { return Native.rdMem(classReference + JOPConstants.CLASS_OFFSET_SUPERCLASS_POINTER); } private static int getPointerToInterfaceTable(int classReference) { return Native.rdMem(classReference + JOPConstants.CLASS_OFFSET_INTERFACE_TABLE_POINTER); } /** * Return the class pointer from an object. * Be careful: if a handle to an array is passed, this method will * return zero. Hence, DON't USE IT WITH ARRAYS!!! it just DOES NOT WORK. * * @param objectHandle * @return */ private static int getClassPointerFromObjectHandle(int objectHandle) { int classPointer = 0; if(isArrayType(objectHandle) == false) { synchronized(GC.getMutex()) { classPointer = getMethodTablePointerOrArrayLength(objectHandle); // fix the offset to point to the class structure (instance size) classPointer -= JOPConstants.CLASS_HEADER_SIZE; } } return classPointer; } /** * Get the value of one instance field. * * @param objectHandle * @param fieldIndex * @return */ private static int getInstanceField(int objectHandle, int fieldIndex) { int size; int value = 0; int objectPointer; synchronized(GC.getMutex()) { // for development only debugPrintObjectHandle(objectHandle); // clear the error flag clearFlagForInstanceFieldAccess(); // get the object size size = getObjectSize(objectHandle); debugPrint(" object size: "); debugPrintln(size); // check if the field index makes sense. Access only if it's a valid one. if(fieldIndex >= 0 && fieldIndex < size) { // get the object pointer objectPointer = getObjectPointer(objectHandle); //use it to access the object content value = Native.rdMem(objectPointer + fieldIndex); } else { // set an error code to inform that this operation failed. setErrorCodeForInstanceFieldAccess(ErrorConstants.ERROR_INVALID_FIELDID); debugPrint(" Invalid index: "); debugPrintln(fieldIndex); debugPrint(" Num. instance locals is: "); debugPrintln(size); } } return value; } /** * Set the value of one instance field. * * @param objectHandle * @param fieldIndex * @param fieldValue * @return */ private static void setInstanceField(int objectHandle, int fieldIndex, int fieldValue) { int size; int objectPointer; synchronized(GC.getMutex()) { // for development only debugPrintObjectHandle(objectHandle); // clear the error flag clearFlagForInstanceFieldAccess(); // get the object size size = getObjectSize(objectHandle); debugPrint(" object size: "); debugPrintln(size); // check if the field index makes sense. Access only if it's a valid one. if(fieldIndex >= 0 && fieldIndex < size) { // get the object pointer objectPointer = getObjectPointer(objectHandle); // // get the correspoding class pointer. // int classPointer = getClassPointerFromObjectHandle(objectHandle); // // // get the object GC info from the class structure. // int gcInfo = Native.rdMem(classPointer // + JOPConstants.CLASS_OFFSET_GC_INFO); // // // check for references. If the field is a reference, // // execute a snapshot-at-beginning write barrier // if((gcInfo & (0x01 << fieldIndex)) != 0) // { // int oldVal = Native.rdMem(objectPointer + fieldIndex); // if(oldVal != 0 && Native.rdMem(oldVal + GC.OFF_SPACE) != GC.toSpace) // { // GC.push(oldVal); // } // } debugPrint("Handle: "); debugPrint(objectHandle); debugPrint(" index: "); debugPrintln(fieldIndex); // request GC to execute the write barrier, if needed. GC.writeBarrier(objectHandle, fieldIndex); // finally, set the field content Native.wrMem(fieldValue, objectPointer + fieldIndex); } else { // set an error code to inform that this operation failed. setErrorCodeForInstanceFieldAccess(ErrorConstants.ERROR_INVALID_FIELDID); debugPrint(" Invalid index: "); debugPrintln(fieldIndex); debugPrint(" Num. instance locals is: "); debugPrintln(size); } } } /** * Clear the error flag. Setup for next access. */ private static void clearFlagForInstanceFieldAccess() { errorCodeForInstanceFieldAccess = 0; } /** * Set an error code to inform that something went wrong. * * @param errorCode */ private static void setErrorCodeForInstanceFieldAccess(int errorCode) { errorCodeForInstanceFieldAccess = errorCode; } /** * Return the error code, if any. * * @return */ private static int getErrorCodeForInstanceFieldAccess() { return errorCodeForInstanceFieldAccess; } /** * Check if the last operation of "instance field access" * was successful or not. If the error code is zero, then * report success. * * @return */ private static boolean isSuccessfulLastInstanceFieldAccess() { return (errorCodeForInstanceFieldAccess == 0); } /** * Handle a "set breakpoint" command. * * @return * @throws IOException */ private static boolean handleSetBreakPointCommand() throws IOException { int methodStructPointer; int instructionOffset; int result; // read the method pointer methodStructPointer = debugChannel.readIntValue(); // read the instruction offset instructionOffset = debugChannel.readIntValue(); debugPrintln("Method body before:"); dumpMethodBody(methodStructPointer); // set the breakpoint result = setBreakPoint(methodStructPointer, instructionOffset); debugPrintln("Method body after:"); dumpMethodBody(methodStructPointer); // send an ack back to keep it in sync. This also inform which instruction // was overwritten, so the debugger can undo it later. if(result != INVALID_INSTRUCTION) { debugChannel.sendReplySetBreakpoint(result); return true; } else { debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_LOCATION); return false; } } /** * Handle a "clear breakpoint" command. * * @return * @throws IOException */ private static boolean handleClearBreakPointCommand() throws IOException { int methodStructPointer; int instructionOffset; int newInstruction; int result; // read the method pointer methodStructPointer = debugChannel.readIntValue(); // read the instruction offset instructionOffset = debugChannel.readIntValue(); // read the new instruction newInstruction = debugChannel.readIntValue(); debugPrintln("Method body before:"); dumpMethodBody(methodStructPointer); // clear the breakpoint result = clearBreakPoint(methodStructPointer, instructionOffset, newInstruction); debugPrintln("Method body after:"); dumpMethodBody(methodStructPointer); // inform if execution was successful or not. if(result != INVALID_INSTRUCTION) { debugChannel.sendReply(); return true; } else { debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_LOCATION); return false; } } /** * Handle a "get method pointer" command. * * This is not a standard JDWP command but is necessary to implement * debugging support in JOP. * * @throws IOException */ private static void handleGetStackFrameMPCommand() throws IOException { int frameIndex = inputStream.readInt(); debugPrint(" Frame index: " ); debugPrint(frameIndex); int pointer; pointer = getFramePointerAtIndex(frameIndex + 1); pointer = getMPFromFrame(pointer); debugPrint(" Method pointer: "); debugPrintln(pointer); writeInt(pointer); } /** * Handle a "Get local variable" command. * * @throws IOException */ private static void handleGetLocalVariableCommand() throws IOException { int frameIndex; int fieldIndex; int value; int pointer; // skip the thread index for now... debugChannel.skipBytes(4); // read the frame index frameIndex = debugChannel.readIntValue(); // skip the number of variables for now. Assume just one. debugChannel.skipBytes(4); // read the field index fieldIndex = debugChannel.readIntValue(); debugPrint(" Frame index: " ); debugPrint(frameIndex); debugPrint(" Variable index: " ); debugPrintln(fieldIndex); pointer = getFramePointerAtIndex(frameIndex); value = getLocalVariable(pointer, fieldIndex); debugPrint(" Value: "); debugPrintln(value); debugPrint(" Pointer: "); debugPrintln(pointer); debugChannel.sendReplyGetLocalVariable(value); } /** * Get the frame pointer at the given index. * * This method allows accesing the call stack as an array, * with the stack frame for the "main" method call * at index zero, the next method (called inside main) at * index one and so on. * * @param frameIndex * @return */ private static int getFramePointerAtIndex(int frameIndex) { int count = getStackDepth(); int pointer = getCurrentFramePointer(); // traverse the stack to find the frame pointer for(int i = 0; i < (count - frameIndex); i++) { pointer = getNextFramePointer(pointer); } return pointer; } /** * Handle a "Set local variable" command. * * @throws IOException */ private static void handleSetLocalVariableCommand() throws IOException { int frameIndex; int fieldIndex; int slotId; int value; int pointer; boolean error = false; // just to make the compier happy;) value = 0; // skip the thread ID for now debugChannel.skipInt(); frameIndex = debugChannel.readIntValue(); // skip the number of variables for now. Assume 1 always. debugChannel.skipInt(); // get the field index fieldIndex = debugChannel.readIntValue(); // get the frame pointer pointer = getFramePointerAtIndex(frameIndex); // get the field type slotId = debugChannel.readByteValue(); debugPrint(" Frame index: " ); debugPrint(frameIndex); debugPrint(" Variable index: " ); debugPrint(fieldIndex); debugPrint(" Slot ID: " ); debugPrint(slotId); debugPrint(" Pointer: " ); debugPrintln(pointer); switch (slotId) { // 1 byte: case TagConstants.BOOLEAN: case TagConstants.BYTE: { debugPrintln("1 byte: "); value = debugChannel.readByteValue(); setLocalVariable(pointer, fieldIndex, value); break; } // 2 bytes: case TagConstants.CHAR: case TagConstants.SHORT: { debugPrintln("2 bytes: "); value = debugChannel.readShortValue(); setLocalVariable(pointer, fieldIndex, value); break; } // 4 bytes: case TagConstants.OBJECT: case TagConstants.ARRAY: case TagConstants.STRING: case TagConstants.FLOAT: case TagConstants.INT: { debugPrintln("4 bytes: "); value = debugChannel.readIntValue(); setLocalVariable(pointer, fieldIndex, value); break; } // 8 bytes: case TagConstants.DOUBLE: case TagConstants.LONG: { debugPrintln("8 bytes: "); value = debugChannel.readIntValue(); setLocalVariable(pointer, fieldIndex, value); value = debugChannel.readIntValue(); setLocalVariable(pointer, fieldIndex + 1, value); break; } // error: those values should not happen. case TagConstants.VOID: case TagConstants.THREAD: case TagConstants.THREAD_GROUP: case TagConstants.CLASS_LOADER: case TagConstants.CLASS_OBJECT: default: { debugPrint("Error. "); error = true; break; } } if(error == false) { debugPrint(" Value: "); debugPrintln(value); debugPrint(" Pointer: "); debugPrintln(pointer); } else { debugPrintln(" Failure setting a local variable!"); } // finally, send a reply to report success or failure. if(error) { debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_FRAMEID); } else { debugChannel.sendReply(); } } /** * Handle a "Get number of local variables" command. * * @throws IOException */ private static void handleGetNumberOfLocalVariablesCommand() throws IOException { int frameIndex; int framePointer; int numLocals; frameIndex = debugChannel.readIntValue(); debugPrint(" Frame index: " ); debugPrint(frameIndex); framePointer = getFramePointerAtIndex(frameIndex); numLocals = getNumLocalsAndParametersFromFrame(framePointer); debugPrint(" Number of local variables: " ); debugPrintln(numLocals); debugChannel.sendReplyFrameCount(numLocals); } /** * Handle a "Get instance variable" command. * * @throws IOException */ private static void handleGetInstanceVariable() throws IOException { int objectHandle; int counter; int numFields; int fieldIndex; int fieldValue; // read the object handle objectHandle = debugChannel.readIntValue(); // What happens if the GC is working and change the pointers // before or during an access? Let's stay on the safe size. // Lock the GC while manipulating objects directly. synchronized(GC.getMutex()) { // check if the reference type is a valid one. if(GC.isValidObjectHandle(objectHandle) == false) { debugPrint("Failure: invalid object handle."); debugPrint(objectHandle); debugPrintln(); // send a packet with an error code and return. debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_OBJECT); return; } // read the number of fields to be accessed. numFields = debugChannel.readIntValue(); // prepare the reply packet debugChannel.prepareInstanceValuesPacket(numFields); // fill in the packet with all information requested for(counter = 0; counter < numFields; counter++) { fieldIndex = debugChannel.readIntValue(); fieldValue = getInstanceField(objectHandle, fieldIndex); // check if an error happened during field access. // if so, stop the loop and send an error packet. if(isSuccessfulLastInstanceFieldAccess() == false) { debugPrint("Failure accessing field # "); debugPrint(counter); debugPrint(". field index: "); debugPrint(fieldIndex); debugPrintln(); break; } debugChannel.writeInstanceFieldValue(fieldValue); } } // end of synchronized block. Now the GC can work freely. // check the error flag. If there's an error, send an error code. // otherwise, all data is correct. Send it to the server. if(isSuccessfulLastInstanceFieldAccess() == false) { // send a packet with an error code. debugChannel.sendReplyWithErrorCode(getErrorCodeForInstanceFieldAccess()); } else { // great! packet ready. Send it to the server. debugChannel.sendInstanceFieldValuesReply(); } } /** * Handle a "Set instance variable" command. * * @throws IOException */ private static void handleSetInstanceVariable() throws IOException { int objectHandle; int counter; int numFields; int fieldIndex; int fieldValue; // read the object handle objectHandle = debugChannel.readIntValue(); // What happens if the GC is working and change the pointers // before or during an access? Let's stay on the safe size. // Lock the GC while manipulating objects directly. synchronized(GC.getMutex()) { // check if the reference type is a valid one. if(GC.isValidObjectHandle(objectHandle) == false) { debugPrint("Failure: invalid object handle."); debugPrint(objectHandle); debugPrintln(); // send a packet with an error code and return. debugChannel.sendReplyWithErrorCode(ErrorConstants.ERROR_INVALID_OBJECT); return; } // read the number of fields to be accessed. numFields = debugChannel.readIntValue(); // fill in the packet with all information requested for(counter = 0; counter < numFields; counter++) { fieldIndex = debugChannel.readIntValue(); fieldValue = debugChannel.readIntValue(); setInstanceField(objectHandle, fieldIndex, fieldValue); // check if an error happened during field access. // if so, stop the loop and send an error packet. if(isSuccessfulLastInstanceFieldAccess() == false) { debugPrint("Failure accessing field # "); debugPrint(counter); debugPrint(". field index: "); debugPrint(fieldIndex); debugPrint(". new field value: "); debugPrint(fieldValue); debugPrintln(); break; } } } // end of synchronized block. Now the GC can work freely. // check the error flag. If there's an error, send an error code. // otherwise, all data is correct. Send it to the server. if(isSuccessfulLastInstanceFieldAccess() == false) { // send a packet with an error code. debugChannel.sendReplyWithErrorCode(getErrorCodeForInstanceFieldAccess()); } else { // great! all operations were done. Send an ack to the server. debugChannel.sendReply(); } } /** * Return the actual object pointer * @param objectHandle * @return */ private static final int getObjectPointer(int objectHandle) { // let's synchronize on the GC to avoid concurrency problems. synchronized(GC.getMutex()) { return Native.rdMem(objectHandle + GC.OFF_PTR); } } /** * Return the instance size. * * @param handle * @return */ private static final int getObjectSize(int objectHandle) { // let's synchronize on the GC to avoid concurrency problems. synchronized(GC.getMutex()) { return Native.rdMem(objectHandle + GC.OFF_SIZE); } } /** * Return the method table pointer (or array length if it's an array). * * @param handle * @return */ private static final int getMethodTablePointerOrArrayLength(int objectHandle) { // let's synchronize on the GC to avoid concurrency problems. synchronized(GC.getMutex()) { return Native.rdMem(objectHandle + GC.OFF_MTAB_ALEN); } } /** * Check if the handle control an array object. * * @param handle * @return */ private static final boolean isArrayType(int objectHandle) { // let's synchronize on the GC to avoid concurrency problems. synchronized(GC.getMutex()) { int type = Native.rdMem(objectHandle + GC.OFF_TYPE); //return (Native.rdMem(objectHandle + GC.OFF_TYPE) == GC.IS_REFARR); //return (Native.rdMem(objectHandle + GC.OFF_TYPE) == Constants.T_ARRAY); return (type == 13 || type == GC.IS_REFARR); } } /** * Test to check if some types of packets are being properly * created. * @throws IOException */ private static void handleTestSendJDWPPackets() throws IOException { int framePointer = getCurrentFramePointer(); int numPackets = 3; debugChannel.sendReplyTestJDWPPackets(numPackets); debugChannel.sendVMStartEvent(); sendBreakpointEvent(framePointer); debugChannel.sendVMDeathEvent(); } /** * Another test. * * @throws IOException */ private static void handleTestReceiveJDWPPackets(int framePointer) throws IOException { int commandSet, command; debugChannel.receivePacket(); commandSet = debugChannel.readInputCommandSet(); command = debugChannel.readInputCommand(); handleJDWPRequest(commandSet, command, framePointer); } /** * @param commandSet * @param command * @throws IOException */ private static void handleJDWPRequest(int commandSet, int command, int framePointer) throws IOException { if((commandSet == 1) && (command == 10)) { debugPrintln("Exit: stop execution."); debugChannel.sendReply(); // TODO: where is the proper place to send a VMDeath event? // maybe after the main method return, too? ;) debugChannel.sendVMDeathEvent(); System.exit(0); } // calculate the stack depth of the caller method. if((commandSet == CommandConstants.ThreadReference_Command_Set) && (command == CommandConstants.ThreadReference_FrameCount)) { debugPrintln("Get stack depth"); // get stack depth of the frame for the breakpoint call int frameCount = getStackDepth(framePointer); debugPrint(" Stack depth of caller method: "); debugPrintln(frameCount); debugChannel.sendReplyFrameCount(frameCount); } } /** * Set a breakpoint instruction. * * Note: this method DOES NOT check instruction boundaries. * * @param methodStructPointer pointer to the method structure * @param instruction * @return */ private static int setBreakPoint(int methodStructPointer, int instructionOffset) { int instruction; // debugPrintln("setBreakPoint(int methodPointer, int instructionOffset)"); instruction = overwriteInstruction(methodStructPointer, instructionOffset, BREAKPOINT_INSTRUCTION); return instruction; } /** * Clear a breakpoint instruction. * * @param methodStructPointer pointer to the method structure * @param instructionOffset * @param newInstruction * @return */ private static int clearBreakPoint(int methodStructPointer, int instructionOffset, int newInstruction) { int instruction; // debugPrintln("clearBreakPoint(int methodStructPointer,int instructionOffset, int newInstruction)"); instruction = overwriteInstruction(methodStructPointer, instructionOffset, newInstruction); return instruction; } /** * Overwrite one method instruction and return the instruction which was * set at that address previously. * * This method DOES NOT check instruction boundaries, it just check * the method length. It allows changing anything inside the method body, * as long as it fits inside. This includes bytecode parameters. * * @param methodStructPointer * @param instructionOffset * @param instruction * @return */ private static int overwriteInstruction(int methodStructPointer, int instructionOffset, int newInstruction) { int methodSize; int startAddress; int instruction = INVALID_INSTRUCTION; int instructionAddress; int word; debugPrintln("setBreakPoint(int methodPointer, int instructionOffset)"); dumpMethodStruct(methodStructPointer); startAddress = getMethodStartAddress(methodStructPointer); // beware: method sizes are in words, not in bytes. And 1 word = 4 bytes. methodSize = getMethodSizeInWords(methodStructPointer); // calculate the method size in bytes. methodSize *= 4; // check if the address is inside the method body. if(instructionOffset >= 0 && instructionOffset < methodSize) { instruction = readBytecode(startAddress, instructionOffset); debugPrintln("Old instruction: " + instruction); writeBytecode(newInstruction, startAddress, instructionOffset); // instructionAddress = startAddress + instructionOffset; // // instruction = Native.rdMem(instructionAddress); // debugPrintln("Old instruction: " + instruction); // // Native.wrMem(newInstruction, instructionAddress); } else { debugPrint("Wrong instruction offset: "); debugPrintln(instructionOffset); debugPrint("Method size:"); debugPrintln(methodSize); debugPrintln(); } return instruction; } /** * Read one byte from the method code. * * Useful to manipulate compiled code, for operations * such as "set breakpoint" or "clear breakpoint". * * @param startAddress * @param instructionOffset * @return */ private static int readBytecode(int startAddress, int instructionOffset) { int word, index; int data; int address; int result = 0; // divide by four. Same as the first line. // word = instructionOffset / 4; word = instructionOffset >> 2; // get the remainder. Same as the first line. // index = instructionOffset % 4; index = instructionOffset & 0x03; address = startAddress + word; data = Native.rdMem(address); // debugPrintln("Word: " + word); // debugPrintln("Remainder: " + index); // debugPrintln("address: " + address); // debugPrint("data: "); // printIntHex(data); // debugPrintln(); switch(index) { case 0: { result = data >>> (3 * 8); break; } case 1: { result = data >>> (2 * 8); break; } case 2: { result = data >>> (1 * 8); break; } case 3: default: { result = data; } } result = result & 0x00ff; // debugPrint("Result: "); // printIntHex(result); // debugPrintln(); return result; } /** * Set one byte from the method code area. * * Basic approach to set a bytecode: * - get the address of the word * - read the word * - clear the old byte * - set the new byte * - write back the word * * @param newInstruction * @param startAddress * @param instructionOffset */ private static void writeBytecode(int newInstruction, int startAddress, int instructionOffset) { int word, index; int data; int address; int result = 0; // divide by four. word = instructionOffset >> 2; // get the remainder. index = instructionOffset & 0x03; address = startAddress + word; data = Native.rdMem(address); // debugPrintln("Word: " + word); // debugPrintln("Remainder: " + index); // debugPrintln("address: " + address); debugPrint("data: "); printIntHex(data); debugPrintln(); // clear all the other bytes, just in case. newInstruction &= 0x00ff; // clear the old byte using a mask and shift the new byte accordingly switch(index) { case 0: { newInstruction = newInstruction << (3 * 8); data = data & MASK_FIRST_BYTE; break; } case 1: { newInstruction = newInstruction << (2 * 8); data = data & MASK_SECOND_BYTE; break; } case 2: { newInstruction = newInstruction << (1 * 8); data = data & MASK_THIRD_BYTE; break; } case 3: default: { data = data & MASK_FOURTH_BYTE; } } // merge the (already shifted) new byte into the word data = data | newInstruction; // finally, write it back into memory. Bytecode changed! Native.wrMem(data, address); debugPrint("new data: "); printIntHex(data); debugPrintln(); } private static void printIntHex(int value) { EmbeddedOutputStream.printIntHex(value, System.out); } private static void testReadInstruction(int instructionAddress) { int instruction; debugPrintln("----------------------------------------"); instruction = Native.rdMem(instructionAddress + 1); debugPrintln("Instruction: " + instruction); instruction = Native.rdMem(instructionAddress + 1); debugPrintln("Instruction: " + instruction); instruction = Native.rdMem(instructionAddress + 2); debugPrintln("Instruction: " + instruction); instruction = Native.rdMem(instructionAddress + 3); debugPrintln("Instruction: " + instruction); debugPrintln("----------------------------------------"); } /** * Return the method start address. * * @param methodPointer * @return */ private static int getMethodStartAddress(int methodPointer) { int startAddress; startAddress = Native.rdMem(methodPointer); // shift the variable 10 bits to the right (unsigned). This is the // method start address. startAddress = startAddress >>> 10; return startAddress; } /** * Return the method size. * * @param methodPointer * @return */ private static int getMethodSizeInWords(int methodPointer) { int startAddress; int methodSize; startAddress = Native.rdMem(methodPointer); // get the last 10 bits: the method length. Hence, size can be up to 1kb. methodSize = startAddress & 0x000003ff; return methodSize; } private static int getMethodConstantPool(int methodPointer) { int data; methodPointer++; data = Native.rdMem(methodPointer); // shift the variable 10 bits to the right (unsigned). This is the // constant pool address. data = data >>> 10; return data; } public static final int getMethodArgCount(int methodPointer) { int data; methodPointer++; data = Native.rdMem(methodPointer); // get the last 5 bits. This is the arg count. data = data & 0x0000001f; return data; } public static final int getMethodLocalsCount(int methodPointer) { int data; methodPointer++; data = Native.rdMem(methodPointer); // shift the variable 5 bits to the right (unsigned). Cut the rest. // This is the locals count. data = data >>> 5; data = data & 0x0000001f; return data; } public static final void dumpMethodStruct(int methodPointer) { int data; debugPrint("Method structure: "); data = Native.rdMem(methodPointer); EmbeddedOutputStream.printIntHex(data, System.out); data = Native.rdMem(methodPointer + 1); EmbeddedOutputStream.printIntHex(data, System.out); debugPrintln(); debugPrintln(); debugPrint("Start address: "); debugPrintln(getMethodStartAddress(methodPointer)); debugPrint("Size (words): "); debugPrintln(getMethodSizeInWords(methodPointer)); debugPrint("Constant pool: "); debugPrintln(getMethodConstantPool(methodPointer)); debugPrint("Local count: "); debugPrintln(getMethodLocalsCount(methodPointer)); debugPrint("Arg count: "); debugPrintln(getMethodArgCount(methodPointer)); debugPrintln(); } public static final int getCurrentMethodPointer() { int data; // get the current frame pointer data = getCurrentFramePointer(); // get the method pointer from the previous method directly from the stack data = getMPFromFrame(data); // debugPrintln("getCurrentMethodPointer()"); return data; } public static final void dumpMethodBody(int methodPointer) { int index, start, size, data; start = getMethodStartAddress(methodPointer); size = getMethodSizeInWords(methodPointer); debugPrintln("Method body:"); debugPrintln(); for(index = 0; index < size; index++) { data = Native.rdMem(start + index); EmbeddedOutputStream.printIntHex(data, System.out); debugPrint(" "); // if((index & 0x07) == 0 && (index > 0)) if(((index + 1)% 0x08) == 0) { // debugPrint("Index:"); // debugPrintln(index); debugPrintln(); } } debugPrintln(); debugPrintln(); } /** * Methods to print the stack content in a way that is * easy to inspect. */ private static void prettyPrintStack() { int framePointer = getCurrentFramePointer(); prettyPrintStack(framePointer); } /** * Methods to print the stack content in a way that is * easy to inspect. * * @param frame * @param previousFrame */ private static void prettyPrintStack(int frame) { int previousFrame; // previousFrame = getCurrentFramePointer(); // frame = getNextFramePointer(previousFrame); frame = getCurrentFramePointer(); while(isFirstFrame(frame) == false) { previousFrame = frame; frame = getNextFramePointer(previousFrame); if(isFirstFrame(frame)) { debugPrintln("Stack frame for main method (next):"); } prettyPrintStackFrame(frame, previousFrame); } // almost done now, but still need to do the last step: // print information for the frame of the main method. // prettyPrintStackFrame(frame, previousFrame); } /** * Pretty print method to show a stack frame internal structure. * Useful for debugging. * * @param frame */ private static void prettyPrintStackFrame(int frame, int previousFrame) { TestJopDebugKernel.printLine(); printVariablesFromFrame(frame); debugPrintln(); printRegistersFromFrame(frame); debugPrintln(); printLocalStackFromFrame(frame, previousFrame); TestJopDebugKernel.printLine(); } /** * @param frame */ private static void printVariablesFromFrame(int frame) { int localPointer; int length; localPointer = getLocalsPointerFromFrame(frame); length = frame - localPointer; // debugPrint(" Local pointer: "); // debugPrint(localPointer); // debugPrint(" Frame pointer: "); // debugPrint(frame); // debugPrint(" Length: "); // debugPrint(length); // debugPrintln(); if(length < 0 || length > MAX_LOCAL_VARIABLES) { debugPrintln("FAILURE!!! wrong local pointer!"); debugPrint(" Local pointer: "); debugPrint(localPointer); debugPrint(" Frame: "); debugPrint(frame); debugPrint(" Max. variables: "); debugPrint(MAX_LOCAL_VARIABLES); return; } debugPrintln("Local variables:"); // print all variables before the frame pointer printStackArea(localPointer, length); } /** * Print the five register fields based on the frame pointer. * * @param frame */ private static void printRegistersFromFrame(int frame) { int value; debugPrint("Previous registers - SP: "); value = getSPFromFrame(frame); debugPrint(value); debugPrint(" PC: "); value = getPCFromFrame(frame); debugPrint(value); debugPrint(" VP: "); value = getVPFromFrame(frame); debugPrint(value); debugPrint(" CP: "); value = getCPFromFrame(frame); debugPrint(value); debugPrint(" MP: "); value = getMPFromFrame(frame); debugPrint(value); debugPrintln(); } /** * Print the local execution stack based on the current frame. * * The previousFrame parameter points to the frame on top of * the one pointed by frame. It is used to delimit the local stack. * It should be greater than frame. * * @param frame */ private static void printLocalStackFromFrame(int frame, int previousFrame) { int localStackPointer, length; // the pointer to the local stack, right after the five frame fields. // Be careful here: this is *NOT* the SP field // (which points to the previous stack top). localStackPointer = frame + 5; // the local execution stack (after a method call has started) // goes from the 5th byte (right after "previous MP" location) // until the position pointed by the "previous SP" field of the // next stack frame (the one of the called method). // Since the "previous SP" in the next frame points to the // stack top, it will point to the "previous MP" in the // current frame when the local stack is empty. length = getSPFromFrame(previousFrame) - localStackPointer + 1; // debugPrint("Frame: "); // debugPrint(frame); // debugPrint(" localStackPointer: "); // debugPrint(localStackPointer); // debugPrint(" previousFrame: "); // debugPrint(previousFrame); // // debugPrint(" length: "); // debugPrint(length); if(length > 0) { debugPrint("Local execution stack size: "); debugPrintln(length); printStackArea(localStackPointer, length); } else { debugPrintln("Local execution stack: empty"); // debugPrintln("Length: " + length); } } /** * Print a set of stack positions in hex format. * It formats the output by inserting a new line for every * 8 words printed. * * @param initialPosition the initial position to be printed. * @param length the number of stack slots to be printed. */ private static void printStackArea(int initialPosition, int length) { int index, data; if(length <= 0) { return; } // print a set of stack words. Do nothing and return, in case of a negative // value for length. for(index = 0; index < length; index++) { data = Native.rdIntMem(initialPosition + index); printIntHex(data); debugPrint(" "); if(((index + 1)% 0x08) == 0) { // debugPrint("Index:"); // debugPrintln(index); debugPrintln(); } } debugPrintln(); } public static void debugPrint(Object data) { if(shouldPrintInternalMessages) { System.out.print(data); } } public static void debugPrintln(Object data) { if(shouldPrintInternalMessages) { System.out.print(data); System.out.println(); } } public static void debugPrint(int data) { if(shouldPrintInternalMessages) { System.out.print(data); } } public static void debugPrintln(int data) { if(shouldPrintInternalMessages) { System.out.println(data); } } public static void debugPrintln() { debugPrintln(""); } /** * For JDWP support development ONLY. * * This method enable internal messages. * It turn on the tracing flag, so internal messages * will be sent to the standard output to help during * development. */ private static void enableDevelopmentInternalMessages() { shouldPrintInternalMessages = true; } /** * For JDWP support development ONLY. * * This method disable internal messages. * It turn off the tracing flag, so internal messages * will *not* be sent to the standard output. */ private static void disableDevelopmentInternalMessages() { shouldPrintInternalMessages = true; } /** * Inform if should print or not messages related to development. * * @return */ public static boolean shouldPrintInternalMessages() { return shouldPrintInternalMessages; } /** * Print information about the object and its class. * For development only. * * @param handle */ private static final void debugPrintObjectHandle(int objectHandle) { if(shouldPrintInternalMessages()) { debugPrintln("Handle information:"); printObjectHandle(objectHandle); debugPrintln(); int classPointer = getClassPointerFromObjectHandle(objectHandle); debugPrintln("Class struct information:"); printClassHeader(classPointer); debugPrintln(); } } /** * Print information about one object handle. * * @param handle */ private static final void printObjectHandle(int handle) { // * According to GC class, the handle contains following data: // * 0 pointer to the object in the heap or 0 when the handle is free // * 1 pointer to the method table or length of an array // * 2 size - could be in class info // * 3 type info: object, primitve array or ref array // * 4 pointer to next handle of same type (used or free) // * 5 gray list // * 6 space marker - either toSpace or fromSpace int data, type; // let's synchronize on the GC to avoid concurrency problems. synchronized(GC.getMutex()) { System.out.print("Handle content: "); for(data = 0; data < 8; data ++) { int value = Native.rdMem(handle + data); EmbeddedOutputStream.printIntHex(value, System.out); debugPrint(" "); } System.out.println(); data = Native.rdMem(handle + GC.OFF_PTR); type = Native.rdMem(handle + GC.OFF_TYPE); if(data == 0) { System.out.println("Free handle: "); System.out.println(handle); } else { System.out.print("Object pointer: "); System.out.println(data); } data = Native.rdMem(handle + GC.OFF_MTAB_ALEN); if(type == GC.IS_OBJ) { System.out.print("Method table: "); } else { if(type == GC.IS_REFARR) { System.out.print("Array length: "); } else { System.out.print("Unknown data: "); } } System.out.println(data); data = Native.rdMem(handle + GC.OFF_SIZE); System.out.print("Instance size: "); System.out.println(data); // System.out.print("Type: "); // System.out.print(type); // System.out.print(" "); if(type == GC.IS_OBJ) { System.out.print("Type: Object (not an array) - "); System.out.println(type); } else { if(type == GC.IS_REFARR) { System.out.println("Type: Array -> "); } else { System.out.println("Type: (?) "); } System.out.println(type); } //next handle data = Native.rdMem(handle + 4); System.out.print("Next handle: "); System.out.println(data); // gray list data = Native.rdMem(handle + 5); System.out.print("Gray list : "); System.out.println(data); // space marker data = Native.rdMem(handle + 6); System.out.print("Space marker: "); System.out.println(data); // if(data == GC.toSpace) // System.out.println("GC class:"); // the code below does not compile: there's one class missing. // System.out.println(GC.class); // this was the last access to the handle content. // close synchronization block and release GC lock. } // if more fields were added, remember to include on the sync block above. } /** * Print information about a class structure. * * Since GC does not affect (modify) classes, it's * not necessary to sync on the GC lock. * * @param classPointer */ private static final void printClassHeader(int classPointer) { // * According to information on the JOP file, the class structure // contains the following data: (the JOP file is verbose, which helps a lot!) // // * 0 instance size // * 1 pointer to static primitive fields // * 2 instance GC info // * 3 pointer to super class // * 4 pointer to interface table int data; if(isValidClassPointer(classPointer) == false) { System.out.print("Failure: invalid class pointer! -> "); System.out.println(classPointer); return; } data = getInstanceSizeFromClass(classPointer); System.out.print("Instance size: "); System.out.println(data); data = getPointerToStaticPrimitiveFields(classPointer); System.out.print("Pointer to static primitive fields: "); System.out.println(data); data = getGCInfo(classPointer); System.out.print("Instance GC info: "); System.out.println(data); data = getPointerToSuperclass(classPointer); System.out.print("Pointer to superclass: "); System.out.println(data); data = getPointerToInterfaceTable(classPointer); System.out.print("Pointer to interface table: "); System.out.println(data); } /** * Check if the class pointer may be valid or not. * * @param classPointer * @return */ private static boolean isValidClassPointer(int classPointer) { int size, specialPointers; int staticReferencePointer, numReferences; // get the application size size = Native.rdMem(0); // get the special pointers specialPointers = Native.rdMem(1); // get reference info staticReferencePointer = Native.rdMem(specialPointers + 4); numReferences = Native.rdMem(specialPointers + 5); // if the pointer is before the end of the static reference area // or after the application size, it's surely a wrong pointer. // Otherwise, it MAY be a valid class pointer, but we can't be sure... if(classPointer < (staticReferencePointer + numReferences) || classPointer > size) { // outside the valid range. It's a wrong pointer, for sure. return false; } //in the valid range. Probably a valid pointer, but may not be. return true; } }