/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.runtime; import org.jikesrvm.VM; import org.jikesrvm.architecture.AbstractRegisters; import org.jikesrvm.architecture.StackFrameLayout; import org.jikesrvm.Options; import org.jikesrvm.classloader.Atom; import org.jikesrvm.classloader.MemberReference; import org.jikesrvm.classloader.RVMMethod; import org.jikesrvm.classloader.NormalMethod; import org.jikesrvm.compilers.baseline.BaselineCompiledMethod; import org.jikesrvm.compilers.common.CompiledMethod; import org.jikesrvm.compilers.common.CompiledMethods; import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod; import org.jikesrvm.compilers.opt.runtimesupport.OptEncodedCallSiteTree; import org.jikesrvm.compilers.opt.runtimesupport.OptMachineCodeMap; import org.jikesrvm.scheduler.RVMThread; import org.vmmagic.pragma.Uninterruptible; import org.vmmagic.pragma.NoInline; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.Offset; /** * A list of compiled method and instructionOffset pairs that describe the state * of the call stack at a particular instant. */ public class StackTrace { /** * Prints an internal stack trace for every stack trace obtained via * {@link #getStackTrace(Throwable)}. The internal stack trace * has machine code offsets and bytecode index information for methods. */ private static final boolean PRINT_INTERNAL_STACK_TRACE = false; /** * The compiled method ids of the stack trace. Ordered with the top of the stack at * 0 and the bottom of the stack at the end of the array */ private final int[] compiledMethods; /** The offset of the instruction within the compiled method */ private final int[] instructionOffsets; /** Index of the last stack trace; only used to support VM.VerboseStackTracePeriod */ private static int lastTraceIndex = 0; /** * Create a trace for the call stack of the current thread */ @NoInline public StackTrace() { this(RVMThread.getCurrentThread()); } /** * Constructs a stack trace. * <p> * Note: no inlining directives here because they aren't necessary for correctness. * This class removes all frames belonging to its methods. * * @param rvmThread the thread whose stack is examined. It is the caller's * responsibility to block that thread if required. */ public StackTrace(RVMThread rvmThread) { assertThreadBlockedOrCurrent(rvmThread); boolean isVerbose = false; int traceIndex = 0; if (VM.VerifyAssertions && VM.VerboseStackTracePeriod > 0) { // Poor man's atomic integer, to get through bootstrap synchronized (StackTrace.class) { traceIndex = lastTraceIndex++; } isVerbose = (traceIndex % VM.VerboseStackTracePeriod == 0); } // (1) Count the number of frames comprising the stack. int numFrames = countFramesUninterruptible(rvmThread); // (2) Construct arrays to hold raw data compiledMethods = new int[numFrames]; instructionOffsets = new int[numFrames]; // (3) Fill in arrays recordFramesUninterruptible(rvmThread); // Debugging trick: print every nth stack trace created if (isVerbose) { VM.disableGC(); VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of creating StackTrace # ", traceIndex); RVMThread.dumpStack(); VM.sysWriteln("END Verbosely dumping stack at time of creating StackTrace # ", traceIndex, " ]"); VM.enableGC(); } } private void assertThreadBlockedOrCurrent(RVMThread rvmThread) { if (VM.VerifyAssertions) { if (rvmThread != RVMThread.getCurrentThread()) { rvmThread.monitor().lockNoHandshake(); boolean blocked = rvmThread.isBlocked(); rvmThread.monitor().unlock(); if (!blocked) { String msg = "Can only dump stack of blocked threads if not dumping" + " own stack but thread " + rvmThread + " was in state " + rvmThread.getExecStatus() + " and wasn't blocked!"; VM._assert(VM.NOT_REACHED, msg); } } } } /** * Walk the stack counting the number of stack frames encountered. * The stack being walked is our stack, so code is Uninterruptible to stop the * stack moving. * * @param stackTraceThread the thread whose stack is walked * @return number of stack frames encountered */ @Uninterruptible @NoInline private int countFramesUninterruptible(RVMThread stackTraceThread) { int stackFrameCount = 0; Address fp; /* Stack trace for the thread */ if (stackTraceThread == RVMThread.getCurrentThread()) { fp = Magic.getFramePointer(); } else { AbstractRegisters contextRegisters = stackTraceThread.getContextRegisters(); fp = contextRegisters.getInnermostFramePointer(); } fp = Magic.getCallerFramePointer(fp); while (Magic.getCallerFramePointer(fp).NE(StackFrameLayout.getStackFrameSentinelFP())) { int compiledMethodId = Magic.getCompiledMethodID(fp); if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) { CompiledMethod compiledMethod = CompiledMethods.getCompiledMethod(compiledMethodId); if ((compiledMethod.getCompilerType() != CompiledMethod.TRAP) && compiledMethod.hasBridgeFromNativeAnnotation()) { // skip native frames, stopping at last native frame preceeding the // Java To C transition frame fp = RuntimeEntrypoints.unwindNativeStackFrame(fp); } } stackFrameCount++; fp = Magic.getCallerFramePointer(fp); } //VM.sysWriteln("stack frame count = ",stackFrameCount); return stackFrameCount; } /** * Walk the stack recording the stack frames encountered * The stack being walked is our stack, so code is Uninterrupible to stop the * stack moving. * * @param stackTraceThread the thread whose stack is walked */ @Uninterruptible @NoInline private void recordFramesUninterruptible(RVMThread stackTraceThread) { int stackFrameCount = 0; Address fp; Address ip; /* Stack trace for the thread */ if (stackTraceThread == RVMThread.getCurrentThread()) { fp = Magic.getFramePointer(); } else { AbstractRegisters contextRegisters = stackTraceThread.getContextRegisters(); fp = contextRegisters.getInnermostFramePointer(); } ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); while (Magic.getCallerFramePointer(fp).NE(StackFrameLayout.getStackFrameSentinelFP())) { //VM.sysWriteln("at stackFrameCount = ",stackFrameCount); int compiledMethodId = Magic.getCompiledMethodID(fp); compiledMethods[stackFrameCount] = compiledMethodId; if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) { CompiledMethod compiledMethod = CompiledMethods.getCompiledMethod(compiledMethodId); if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) { instructionOffsets[stackFrameCount] = compiledMethod.getInstructionOffset(ip).toInt(); if (compiledMethod.hasBridgeFromNativeAnnotation()) { //VM.sysWriteln("native!"); // skip native frames, stopping at last native frame preceeding the // Java To C transition frame fp = RuntimeEntrypoints.unwindNativeStackFrame(fp); } } else { //VM.sysWriteln("trap!"); } } else { //VM.sysWriteln("invisible method!"); } stackFrameCount++; ip = Magic.getReturnAddress(fp, stackTraceThread); fp = Magic.getCallerFramePointer(fp); } } /** Class to wrap up a stack frame element */ public static class Element { /** Stack trace's method, null => invisible or trap */ protected final RVMMethod method; /** Line number of element */ protected final int lineNumber; /** Is this an invisible method? */ protected final boolean isInvisible; /** Is this a hardware trap method? */ protected final boolean isTrap; /** * Constructor for non-opt compiled methods * @param cm the compiled method * @param off offset of the instruction from start of machine code, * in bytes */ Element(CompiledMethod cm, int off) { isInvisible = (cm == null); if (!isInvisible) { isTrap = cm.getCompilerType() == CompiledMethod.TRAP; if (!isTrap) { method = cm.getMethod(); lineNumber = cm.findLineNumberForInstruction(Offset.fromIntSignExtend(off)); } else { method = null; lineNumber = 0; } } else { isTrap = false; method = null; lineNumber = 0; } } /** * Constructor for opt compiled methods. * @param method the method that was called * @param ln the line number */ Element(RVMMethod method, int ln) { this.method = method; lineNumber = ln; isTrap = false; isInvisible = false; } /** @return source file name */ public String getFileName() { if (isInvisible || isTrap) { return null; } else { Atom fn = method.getDeclaringClass().getSourceName(); return (fn != null) ? fn.toString() : null; } } public String getClassName() { if (isInvisible || isTrap) { return ""; } else { return method.getDeclaringClass().toString(); } } public Class<?> getElementClass() { if (isInvisible || isTrap) { return null; } return method.getDeclaringClass().getClassForType(); } public String getMethodName() { if (isInvisible) { return "<invisible method>"; } else if (isTrap) { return "<hardware trap>"; } else { if (method != null) { return method.getName().toString(); } else { return "<unknown method: method was null>"; } } } public int getLineNumber() { return lineNumber; } public boolean isNative() { if (isInvisible || isTrap) { return false; } else { return method.isNative(); } } } /** * A stack trace element that contains additional debugging information, * namely machine code offsets and byte code indexes. */ static class InternalStackTraceElement extends Element { /** machine code offset */ private Offset mcOffset; /** byte code index */ private int bci; /** * Constructor for non-opt compiled methods * @param cm the compiled method * @param off offset of the instruction from start of machine code, * in bytes */ InternalStackTraceElement(CompiledMethod cm, int off) { super(cm, off); if (!isInvisible) { if (!isTrap) { Offset machineCodeOffset = Offset.fromIntSignExtend(off); mcOffset = machineCodeOffset; if (cm instanceof BaselineCompiledMethod) { bci = ((BaselineCompiledMethod) cm).findBytecodeIndexForInstruction(machineCodeOffset); } else if (cm instanceof OptCompiledMethod) { bci = ((OptCompiledMethod) cm).getMCMap().getBytecodeIndexForMCOffset(machineCodeOffset); } else { bci = 0; } } else { mcOffset = Offset.zero(); bci = 0; } } else { mcOffset = Offset.zero(); bci = 0; } } /** * Constructor for opt compiled methods. * @param method the method that was called * @param ln the line number * @param mcOffset the machine code offset for the line * @param bci the bytecode index for the line * */ InternalStackTraceElement(RVMMethod method, int ln, Offset mcOffset, int bci) { super(method, ln); this.mcOffset = mcOffset; this.bci = bci; } void printForDebugging() { VM.sysWrite("{IST: "); VM.sysWrite(method.getDeclaringClass().toString()); VM.sysWrite("."); VM.sysWrite(method.getName()); VM.sysWrite(" --- "); VM.sysWrite("line_number: "); VM.sysWrite(lineNumber); VM.sysWrite(" byte_code_index: "); VM.sysWrite(bci); VM.sysWrite(" machine_code_offset: "); VM.sysWrite(mcOffset); VM.sysWriteln(); } } private CompiledMethod getCompiledMethod(int element) { if ((element >= 0) && (element < compiledMethods.length)) { int mid = compiledMethods[element]; if (mid != StackFrameLayout.getInvisibleMethodID()) { return CompiledMethods.getCompiledMethod(mid); } } return null; } /** * @param cause the throwable that caused the stack trace * @return the stack trace for use by the Throwable API */ public Element[] getStackTrace(Throwable cause) { int first = firstRealMethod(cause); int last = lastRealMethod(first); Element[] elements = buildStackTrace(first, last); if (PRINT_INTERNAL_STACK_TRACE) { VM.sysWriteln(); for (Element e : elements) { InternalStackTraceElement internalEle = (InternalStackTraceElement) e; internalEle.printForDebugging(); } } return elements; } private Element createStandardStackTraceElement(CompiledMethod cm, int off) { if (!PRINT_INTERNAL_STACK_TRACE) { return new Element(cm, off); } else { return new InternalStackTraceElement(cm, off); } } private Element createOptStackTraceElement(RVMMethod m, int ln, Offset mcOffset, int bci) { if (!PRINT_INTERNAL_STACK_TRACE) { return new Element(m, ln); } else { return new InternalStackTraceElement(m, ln, mcOffset, bci); } } private Element[] buildStackTrace(int first, int last) { Element[] elements = new Element[countFrames(first, last)]; if (!VM.BuildForOptCompiler) { int element = 0; for (int i = first; i <= last; i++) { elements[element] = createStandardStackTraceElement(getCompiledMethod(i), instructionOffsets[i]); element++; } } else { int element = 0; for (int i = first; i <= last; i++) { CompiledMethod compiledMethod = getCompiledMethod(i); if ((compiledMethod == null) || (compiledMethod.getCompilerType() != CompiledMethod.OPT)) { // Invisible or non-opt compiled method elements[element] = createStandardStackTraceElement(compiledMethod, instructionOffsets[i]); element++; } else { Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]); OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod; OptMachineCodeMap map = optInfo.getMCMap(); int iei = map.getInlineEncodingForMCOffset(instructionOffset); if (iei < 0) { elements[element] = createStandardStackTraceElement(compiledMethod, instructionOffsets[i]); element++; } else { int[] inlineEncoding = map.inlineEncoding; int bci = map.getBytecodeIndexForMCOffset(instructionOffset); for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) { int mid = OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding); RVMMethod method = MemberReference.getMethodRef(mid).getResolvedMember(); int lineNumber = ((NormalMethod)method).getLineNumberForBCIndex(bci); elements[element] = createOptStackTraceElement(method, lineNumber, instructionOffset, bci); element++; if (iei > 0) { bci = OptEncodedCallSiteTree.getByteCodeOffset(iei, inlineEncoding); } } } } } } return elements; } /** * Count number of stack frames including those inlined by the opt compiler * @param first the first compiled method to look from * @param last the last compiled method to look to * @return the number of stack frames */ private int countFrames(int first, int last) { int numElements = 0; if (!VM.BuildForOptCompiler) { numElements = last - first + 1; } else { for (int i = first; i <= last; i++) { CompiledMethod compiledMethod = getCompiledMethod(i); if ((compiledMethod == null) || (compiledMethod.getCompilerType() != CompiledMethod.OPT)) { // Invisible or non-opt compiled method numElements++; } else { Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]); OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod; OptMachineCodeMap map = optInfo.getMCMap(); int iei = map.getInlineEncodingForMCOffset(instructionOffset); if (iei < 0) { numElements++; } else { int[] inlineEncoding = map.inlineEncoding; for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) { numElements++; } } } } } return numElements; } /** * Find the first non-VM method/exception initializer method in the stack * trace. As we're working with the compiled methods we're assuming the * constructor of the exception won't have been inlined into the throwing * method. * * @param cause the cause of generating the stack trace marking the end of the * frames to elide * @return the index of the method throwing the exception or else 0 */ private int firstRealMethod(Throwable cause) { /* We expect a hardware trap to look like: * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78) * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67) * at java.lang.Throwable.fillInStackTrace(Throwable.java:498) * at java.lang.Throwable.<init>(Throwable.java:159) * at java.lang.Throwable.<init>(Throwable.java:147) * at java.lang.Exception.<init>(Exception.java:66) * at java.lang.RuntimeException.<init>(RuntimeException.java:64) * at java.lang.NullPointerException.<init>(NullPointerException.java:69) * at org.jikesrvm.runtime.RuntimeEntrypoints.deliverHardwareException(RuntimeEntrypoints.java:682) * at <hardware trap>(Unknown Source:0) * * and a software trap to look like: * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78) * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67) * at java.lang.Throwable.fillInStackTrace(Throwable.java:498) * at java.lang.Throwable.<init>(Throwable.java:159) * at java.lang.Error.<init>(Error.java:81) * at java.lang.LinkageError.<init>(LinkageError.java:72) * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:85) * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:75) * * and an OutOfMemoryError to look like: * ??? * ... * at org.jikesrvm.mm.mminterface.MemoryManager.allocateSpace(MemoryManager.java:613) * ... * at org.jikesrvm.runtime.RuntimeEntrypoints.unresolvedNewArray(RuntimeEntrypoints.java:401) */ if (Options.stackTraceFull) { return 0; } else { int element = 0; CompiledMethod compiledMethod = getCompiledMethod(element); // Deal with OutOfMemoryError if (cause instanceof OutOfMemoryError) { // (1) search until RuntimeEntrypoints while ((element < compiledMethods.length) && (compiledMethod != null) && compiledMethod.getMethod().getDeclaringClass().getClassForType() != RuntimeEntrypoints.class) { element++; compiledMethod = getCompiledMethod(element); } // (2) continue until not RuntimeEntrypoints while ((element < compiledMethods.length) && (compiledMethod != null) && compiledMethod.getMethod().getDeclaringClass().getClassForType() == RuntimeEntrypoints.class) { element++; compiledMethod = getCompiledMethod(element); } return element; } // (1) remove any StackTrace frames element = removeStackTraceFrames(element); compiledMethod = getCompiledMethod(element); // (2) remove any VMThrowable frames if (VM.BuildForGnuClasspath) { while ((element < compiledMethods.length) && (compiledMethod != null) && compiledMethod.getMethod().getDeclaringClass().getClassForType().getName().equals("java.lang.VMThrowable")) { element++; compiledMethod = getCompiledMethod(element); } } // (3) remove any Throwable frames while ((element < compiledMethods.length) && (compiledMethod != null) && compiledMethod.getMethod().getDeclaringClass().getClassForType() == java.lang.Throwable.class) { element++; compiledMethod = getCompiledMethod(element); } // (4) remove frames belonging to exception constructors upto the causes constructor while ((element < compiledMethods.length) && (compiledMethod != null) && (compiledMethod.getMethod().getDeclaringClass().getClassForType() != cause.getClass()) && compiledMethod.getMethod().isObjectInitializer() && compiledMethod.getMethod().getDeclaringClass().isAssignableToThrowable()) { element++; compiledMethod = getCompiledMethod(element); } // (5) remove frames belonging to the causes constructor // NB This can be made to incorrectly elide frames if the cause // exception is thrown from a constructor of the cause exception, however, // Sun's VM has the same problem while ((element < compiledMethods.length) && (compiledMethod != null) && (compiledMethod.getMethod().getDeclaringClass().getClassForType() == cause.getClass()) && compiledMethod.getMethod().isObjectInitializer()) { element++; compiledMethod = getCompiledMethod(element); } // (6) remove possible RuntimeEntrypoints.raise* methods used by // PPC opt compiler. Note: only one of the methods can be present at // a time! if ((element < compiledMethods.length) && (compiledMethod != null) && Entrypoints.isInvisibleRaiseMethod(compiledMethod.getMethod())) { element++; compiledMethod = getCompiledMethod(element); } // (7) remove possible hardware exception deliverer frames if (element < compiledMethods.length - 2) { compiledMethod = getCompiledMethod(element + 1); if ((compiledMethod != null) && compiledMethod.getCompilerType() == CompiledMethod.TRAP) { element += 2; } } return element; } } /** * Finds the first non-VM method in the stack trace. In this case, the assumption * is that no exception occurred which makes the job of this method much easier * than of {@link #firstRealMethod(Throwable)}: it is only necessary to skip * frames from this class. * * @return the index of the method or else 0 */ private int firstRealMethod() { return removeStackTraceFrames(0); } /** * Removes all frames from the StackTrace class (i.e. this class) from * the stack trace by skipping them. * <p> * Note: Callers must update all data relating to the element index themselves. * * @param element the element index * @return an updated element index */ private int removeStackTraceFrames(int element) { CompiledMethod compiledMethod = getCompiledMethod(element); while ((element < compiledMethods.length) && (compiledMethod != null) && compiledMethod.getMethod().getDeclaringClass().getClassForType() == StackTrace.class) { element++; compiledMethod = getCompiledMethod(element); } return element; } /** * Find the first non-VM method at the end of the stack trace * @param first the first real method of the stack trace * @return compiledMethods.length-1 if no non-VM methods found else the index of * the method */ private int lastRealMethod(int first) { /* We expect an exception on the main thread to look like: * at <invisible method>(Unknown Source:0) * at org.jikesrvm.runtime.Reflection.invoke(Reflection.java:132) * at org.jikesrvm.scheduler.MainThread.run(MainThread.java:195) * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534) * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113 * * and on another thread to look like: * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534) * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113) */ int max = compiledMethods.length - 1; if (Options.stackTraceFull) { return max; } else { // Start at end of array and elide a frame unless we find a place to stop for (int i = max; i >= first; i--) { if (compiledMethods[i] == StackFrameLayout.getInvisibleMethodID()) { // we found an invisible method, assume next method if this is sane if (i - 1 >= 0) { return i - 1; } else { return max; // not sane => return max } } CompiledMethod compiledMethod = getCompiledMethod(i); if (compiledMethod.getCompilerType() == CompiledMethod.TRAP) { // looks like we've gone too low return max; } Class<?> frameClass = compiledMethod.getMethod().getDeclaringClass().getClassForType(); if ((frameClass != org.jikesrvm.scheduler.MainThread.class) && (frameClass != org.jikesrvm.scheduler.RVMThread.class) && (frameClass != org.jikesrvm.runtime.Reflection.class)) { // Found a non-VM method return i; } } // No frame found return max; } } /** * Gets a stack trace at the current point in time, assuming the thread didn't * throw any exception. * * @param framesToSkip count of frames to skip. Note: frames from this class are always * skipped and thus not included in the count. For example, if the caller were * {@code foo()} and you wanted to skip {@code foo}'s frame, you would pass * {@code 1}. * * @return a stack trace */ public Element[] stackTraceNoException(int framesToSkip) { if (VM.VerifyAssertions) VM._assert(framesToSkip >= 0, "Cannot skip negative amount of frames"); int first = firstRealMethod(); first += framesToSkip; int last = lastRealMethod(first); return buildStackTrace(first, last); } }