package xtc.lang.blink; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.io.IOException; import xtc.lang.blink.CallStack.NativeCallFrame.NativeFrameType; /** * The Java/C mixed call stack representation. * * @author Byeongcheol Lee */ public final class CallStack { /** The language of the stack frame. */ static enum FrameLanguage {JAVA, C, JEANNIE}; /** * Extract a mixed language call stack. * * @param dbg The Blink debugger. * @param remapper The symbol remapper. * @return The call stack. */ public static CallStack extractCallStack(Blink dbg, SymbolMapper remapper) throws IOException { FrameLanguage lang; List<JavaCallFrame> jFrames; List<NativeCallFrame> nFrames; switch (dbg.getDebugControlStatus()) { case JDB: lang = FrameLanguage.JAVA; // get java frames jFrames = dbg.jdb.getFrames(); // get C frames dbg.j2c(); nFrames = dbg.ndb.getFrames(); dbg.jret(); //remove naive frames due to the j2c transition. nFrames.remove(0); nFrames.remove(0); break; case GDB: lang = FrameLanguage.C; // get C frames nFrames = dbg.ndb.getFrames(); // get Java frames. dbg.c2j(); jFrames = dbg.jdb.getFrames(); dbg.jret(); break; default: assert false : "should not be reachable."; return null; } if (dbg.options.getVerboseLevel() >=1) { dbg.out("building mixed frames\n"); dbg.out(toString(jFrames, nFrames)); } // build mixed frames. List<MicroCallFrame> interleaved = interleaveFrames(lang, jFrames, nFrames); List<MicroCallFrame> simplified = simplifyFrames(interleaved); List<ICallFrame> mixed = processJeannieFrames(simplified, remapper); CallStack callStack = new CallStack(mixed); return callStack; } /** * Interleave call frames beginning with the current language. * * @param lang The top frame language. * @param jframes The Java frames. * @param nframes The native frames. * @return The interleaved frames. */ private static List<MicroCallFrame> interleaveFrames(FrameLanguage lang, List<JavaCallFrame> jframes, List<NativeCallFrame> nframes) { LinkedList<MicroCallFrame> interleaved = new LinkedList<MicroCallFrame>(); ListIterator<JavaCallFrame> jframeIt = jframes.listIterator(); ListIterator<NativeCallFrame> nframeIt = nframes.listIterator(); NativeCallFrame nframe = nframeIt.hasNext() ? nframeIt.next() : null; JavaCallFrame jframe = jframeIt.hasNext() ? jframeIt.next() : null; while(nframe != null || jframe != null) { switch(lang) { case C: if (nframe != null) { interleaved.addLast(nframe); if (nframe.getType() == NativeFrameType.J2C_PROXY) { lang = FrameLanguage.JAVA; assert jframe != null && jframe.isJavaNativeMethod(); } nframe = nframeIt.hasNext() ? nframeIt.next() : null; } else { lang = FrameLanguage.JAVA; } break; case JAVA: if (jframe != null) { interleaved.addLast(jframe); if (jframeIt.hasNext()) { jframe = jframeIt.next(); if (jframe.isJavaNativeMethod()) { lang = FrameLanguage.C; assert nframe != null; assert nframe.getType() == NativeFrameType.C2J_PROXY; } } else { jframe = null; } } else { lang = FrameLanguage.C; } break; } } return interleaved; } /** * Simplify the input call frames by eliminating agent frames and redundant * frames. * * @param interleaved The interleaved frames. * @return The simplified frames. */ private static List<MicroCallFrame> simplifyFrames( List<MicroCallFrame> interleaved) { LinkedList<MicroCallFrame> simplified = new LinkedList<MicroCallFrame>(); for(final MicroCallFrame frame: interleaved) { if (frame instanceof JavaCallFrame) { JavaCallFrame jframe = (JavaCallFrame)frame; if (!jframe.isJavaNativeMethod()) { simplified.add(jframe); } } else if (frame instanceof NativeCallFrame) { NativeCallFrame nframe = (NativeCallFrame)frame; NativeFrameType type = nframe.getType(); switch(type) { case NORMAL: simplified.add(nframe); break; case J2C_PROXY: case C2J_PROXY: break; default: assert false : "not reachable"; break; } } else { assert false : "not reachable"; } } return simplified; } /** * Condense a sequence Java and native frames into a single Jeannie frame. * Condense as much as possible, and return mixed Java, native and Jeannie * frames. * * @param frames The interleaved frame. * @param remapper The symbol remapper. * @return The mixed frames. */ private static List<ICallFrame> processJeannieFrames( List<MicroCallFrame> frames, SymbolMapper remapper) { List<ICallFrame> mixedFrames = new LinkedList<ICallFrame>(); LinkedList<MicroCallFrame> framesForJeannie = null; String jeannieMethodName = null; for(final MicroCallFrame frame :frames) { String sname = frame.getSymbolName(); String srcName = frame.getSourceFile(); SymbolMapper.MethodRemapEntry e = remapper.lookupMethodRemap(sname, srcName); boolean isJeannieMicroFrame = (e != null); if (isJeannieMicroFrame) { if (framesForJeannie == null) { // The start of the Jeannie frame. jeannieMethodName = e.getSourceLanguageName(); framesForJeannie = new LinkedList<MicroCallFrame>(); } // The middle or the end of Jeannie frame. framesForJeannie.add(frame); } else { //Flush any pending Jeannie frame. if (framesForJeannie != null) { mixedFrames.add(new JeannieCallFrame(jeannieMethodName, framesForJeannie)); framesForJeannie = null; } // add a normal Java/C frame. mixedFrames.add(frame); } } // Flush any pending Jeannie frame. if (framesForJeannie != null) { mixedFrames.add(new JeannieCallFrame(jeannieMethodName, framesForJeannie)); framesForJeannie = null; } return mixedFrames; } /** * Create a user readable representation of java and native frames for * debugging purpose. * * @param jframes The Java frames. * @param nframes The native frames. * @return The user readable string representation. */ private static String toString(List<JavaCallFrame> jframes, List<NativeCallFrame> nframes) { StringBuffer buf = new StringBuffer(); buf.append("Dumping java and native frames\n"); buf.append("Java frames: \n"); for(JavaCallFrame f : jframes) { buf.append(f.toString()).append('\n'); } buf.append("Native frames: \n"); for(NativeCallFrame f : nframes) { buf.append(f.toString()).append('\n'); } return buf.toString(); } /** a list of Java, C, and Jeannie call frames. */ private final List<ICallFrame> mixedFrames; /** * The constructor. * * @param mixedFrames The mixed Java/C frame. * @param topFrameLanguage The language of the top frame. * @param javaFrames The list of Java call frames. * @param nativeFrames The list of Native call frames. */ private CallStack(List<ICallFrame> mixedFrames) { this.mixedFrames= mixedFrames; } /** * Get mixed stack frame by specifying its id. * * @param id the frame id. */ public ICallFrame getCallFrame(int id) { assert id >= 0 && id < size(); return mixedFrames.get(id); } /** * Try to get Java frame at a frame id. If the frame at the id is Java frame, * just return this frame. If the frame is a Jeannie macro frame, try obtaining * a Java frame within the Jeannie macro frame. Otherwise, just return null. * * @param id The frame identifier. * @return The Java frame or null. */ public NativeCallFrame getNativeCallFrame(int id) { ICallFrame f = getCallFrame(id); if (f instanceof NativeCallFrame) { return (NativeCallFrame)f; } else if (f instanceof JeannieCallFrame) { JeannieCallFrame jeannieFrame = (JeannieCallFrame)f; return jeannieFrame.getTopNativeFrame(); } else { assert f instanceof JavaCallFrame; return null; } } /** * Try to get Native frame at a frame id. If the frame at the id is native * frame, just return this frame. If the frame is a Jeannie macro frame, try * obtaining a native frame within the Jeannie macro frame. Otherwise, just * return null. * * @param id The frame identifier. * @return The native frame or null. */ public JavaCallFrame getJavaCallFrame(int id) { ICallFrame frame = getCallFrame(id); if (frame instanceof JavaCallFrame) { return (JavaCallFrame)frame; } else if (frame instanceof JeannieCallFrame) { JeannieCallFrame jeannieFrame = (JeannieCallFrame)frame; return jeannieFrame.getTopJavaFrame(); } else { assert frame instanceof NativeCallFrame; return null; } } /** * Get the number of the mixed frames. * * @return The size of the mixed frame. */ public int size() { return mixedFrames.size(); } /** * Get the readable representation of this mixed frame. * * @return The string representation. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Mixed frames\n"); int i = 0; for(final ICallFrame f : mixedFrames) { sb.append(" [" + (i++) + "]" + f + "\n"); } return sb.toString(); } /** * The unified call frame interface for mixed langauage. */ static interface ICallFrame { /** Getter for the line number. */ public int getLineNumber(); /** Getter for the source file. */ public String getSourceFile(); /** Getter for the top micro frame. */ public MicroCallFrame getTopMicroFrame(); /** Get the langauge. */ public FrameLanguage getLanguage(); } /** * A base class for the mixed language frame. */ static abstract class MicroCallFrame implements ICallFrame { /** The source file name. */ private final String sourceFile; /** The line number.*/ private final int lineNumber; /** The constructor. */ protected MicroCallFrame(final String sourceFile, final int lineNumber) { this.sourceFile = sourceFile; this.lineNumber = lineNumber; } /** Getter method for the line number. */ public final int getLineNumber() { return lineNumber; } /** Getter method for the source file. */ public final String getSourceFile() { return sourceFile; } /** Getter method for the symbol name. */ public abstract String getSymbolName(); } /** * A Java call frame representation. */ static class JavaCallFrame extends MicroCallFrame { /** The jdb indentifier. */ private final int frameID; /** The class name. */ private final String className; /** The method name. */ private final String methodName; /** Wheather or not Java native method. */ private final boolean isJavaNativeMethod; /** * The constructor. * * @param jdbIdentifier The jdb identifier. * @param sourceFile The source file name. * @param lineNumber The line number. * @param isJavaNativeMethod The Java native method flag. * @param className The class name. * @param methodName The method name. */ protected JavaCallFrame(int jdbIdentifier, String sourceFile, int lineNumber, boolean isJavaNativeMethod, String className, String methodName) { super(sourceFile, lineNumber); /** The identifier that the gdb can recognize.*/ this.frameID = jdbIdentifier; this.className = className; this.methodName = methodName; this.isJavaNativeMethod = isJavaNativeMethod; } /** Getters. */ public int getJdbIdentifier() { return frameID; } public final String getClassName() { return className; } public final String getMethodName() { return methodName; } public FrameLanguage getLanguage() { return FrameLanguage.JAVA; } public String getSymbolName() { return className+ "."+methodName; } public MicroCallFrame getTopMicroFrame() { return this; } public final boolean isJavaNativeMethod() { return isJavaNativeMethod; } /** * Get readable representation. * * @return The representation. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append(className); sb.append('.'); sb.append(methodName); if (getSourceFile() != null && getLineNumber() >= 1) { sb.append(" (" + getSourceFile() + ":" + getLineNumber() + ")"); } else { sb.append(" (native method)"); } sb.append(" Java"); return sb.toString(); } } /** * The Native call frame representation. */ static class NativeCallFrame extends MicroCallFrame { public enum NativeFrameType {NORMAL, J2C_PROXY, C2J_PROXY}; /** The native debugger specific frame identifier.*/ private final int frameID; /** The function name.*/ private final String functionName; /** The frame type. */ private final NativeFrameType type; /** The constructor. */ protected NativeCallFrame(int frameID, String functionName, String sourceFile, int lineNumber, NativeFrameType type) { super(sourceFile, lineNumber); this.frameID = frameID; this.functionName = functionName; this.type = type; } /** Getter method for the gdb identifier. */ public int getFrameID() { return frameID; } /** Getter method for gdb function symbol name.*/ public final String getFunctionName() { return functionName; } /** Getter method for the language. */ public FrameLanguage getLanguage() { return FrameLanguage.C; } /** Getter method for top micro frame. */ public MicroCallFrame getTopMicroFrame() { return this; } /** Geter method for tye symbol name. */ public String getSymbolName() { return functionName; } public final NativeFrameType getType() {return type;} /** * @return The string representation. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append(functionName); if (getSourceFile() != null && getLineNumber() >= 1) { sb.append(" (" + getSourceFile() + ":" + getLineNumber() + ")"); } sb.append(" C"); return sb.toString(); } } /** * The Jeannie call frame representation. A list of nonempty micro Java/C * frames makes a single macro Jeannie call frame. The first Java/C frame in * this list is close to the top of stack frame, and this frame is * representive of the current program counter. Therefore, a number of the * ICallFrame interface method calls are delegated to the same method of the * first element. */ static class JeannieCallFrame implements ICallFrame { /** The user visible name of this Jeannie frame. */ final String name; /** The list of Java/Native frames. */ final MicroCallFrame[] frames; /** * @param name The name of this Jeannie frame. * @param frameList The list of Java/Native frames. */ private JeannieCallFrame(String name, List<MicroCallFrame> frameList) { this.name = name; this.frames = frameList.toArray(new MicroCallFrame[0]); assert frames != null && frames.length > 0; } /** Getter method for the line number. */ public int getLineNumber() { return frames[0].getLineNumber(); } /** Getter method for the source file. */ public String getSourceFile() { return frames[0].getSourceFile(); } /** Getter method for the language transition flag. */ public boolean isTransition() { return false; } /** Getter method for the language. */ public FrameLanguage getLanguage() { return FrameLanguage.JEANNIE; } /** Getter method for the top micro frame. */ public MicroCallFrame getTopMicroFrame() { return frames[0]; } /** Getter method for the micro frame. */ public MicroCallFrame getMicroFrame(int i) { assert i >=0 && i < frames.length; return frames[i]; } /** Gettter method for the number of micro frames. */ public int getNumberofMicroFrames() { return frames.length; } /** Getter method for the top Java frame. */ public JavaCallFrame getTopJavaFrame() { for(int i = 0;i <frames.length;i++) { MicroCallFrame f = frames[i]; if (f instanceof JavaCallFrame) { return (JavaCallFrame)f; } } return null; } /** Getter method for the top native frame.*/ public NativeCallFrame getTopNativeFrame() { for(int i = 0;i <frames.length;i++) { MicroCallFrame f = frames[i]; if (f instanceof NativeCallFrame) { return (NativeCallFrame)f; } } return null; } /** * Obtain a string representation. * * @return The string representation. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append(name); String sourceFile = getSourceFile(); int lineNumber = getLineNumber(); if (sourceFile != null && lineNumber >= 1) { sb.append(" (" + sourceFile + ":" + lineNumber + ")"); } sb.append(" Jeannie"); return sb.toString(); } } /** * A Local variable in the frame. */ static class LocalVariable { /** The name of the local variable. */ private final String name; /** The value of the local variable. */ private final String value; /** * Constructor. * * @param name The name. * @param value The value. */ public LocalVariable(String name, String value) { this.name = name; this.value = value; } /** Getter method for the name. */ public String getName() {return name;} /** Getter method for the value. */ public String getValue() {return value;} } }