/* * 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 static org.jikesrvm.ArchitectureSpecific.StackframeLayoutConstants.INVISIBLE_METHOD_ID; import static org.jikesrvm.ArchitectureSpecific.StackframeLayoutConstants.STACKFRAME_SENTINEL_FP; import org.jikesrvm.VM; 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.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 { /** * 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 RVMThread.getThreadForStackTrace * (normally the current thread unless we're in GC) */ @NoInline public StackTrace() { 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); } RVMThread stackTraceThread = RVMThread.getCurrentThread().getThreadForStackTrace(); if (stackTraceThread != RVMThread.getCurrentThread()) { // (1) Count the number of frames comprising the stack. int numFrames = countFramesNoGC(stackTraceThread); // (2) Construct arrays to hold raw data compiledMethods = new int[numFrames]; instructionOffsets = new int[numFrames]; // (3) Fill in arrays recordFramesNoGC(stackTraceThread); } else { // (1) Count the number of frames comprising the stack. int numFrames = countFramesUninterruptible(stackTraceThread); // (2) Construct arrays to hold raw data compiledMethods = new int[numFrames]; instructionOffsets = new int[numFrames]; // (3) Fill in arrays recordFramesUninterruptible(stackTraceThread); } // 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(); } } /** * Walk the stack counting the number of stack frames encountered. * The stack being walked isn't our stack so GC must be disabled. * @return number of stack frames encountered */ private int countFramesNoGC(RVMThread stackTraceThread) { int stackFrameCount = 0; VM.disableGC(); // so fp & ip don't change under our feet Address fp; Address ip; /* Stack trace for a sleeping thread */ fp = stackTraceThread.contextRegisters.getInnermostFramePointer(); ip = stackTraceThread.contextRegisters.getInnermostInstructionAddress(); while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) { int compiledMethodId = Magic.getCompiledMethodID(fp); if (compiledMethodId != INVISIBLE_METHOD_ID) { 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++; ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); } VM.enableGC(); return stackFrameCount; } /** * Walk the stack recording the stack frames encountered. * The stack being walked isn't our stack so GC must be disabled. */ private void recordFramesNoGC(RVMThread stackTraceThread) { int stackFrameCount = 0; VM.disableGC(); // so fp & ip don't change under our feet Address fp; Address ip; /* Stack trace for a sleeping thread */ fp = stackTraceThread.contextRegisters.getInnermostFramePointer(); ip = stackTraceThread.contextRegisters.getInnermostInstructionAddress(); while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) { int compiledMethodId = Magic.getCompiledMethodID(fp); compiledMethods[stackFrameCount] = compiledMethodId; if (compiledMethodId != INVISIBLE_METHOD_ID) { CompiledMethod compiledMethod = CompiledMethods.getCompiledMethod(compiledMethodId); if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) { instructionOffsets[stackFrameCount] = compiledMethod.getInstructionOffset(ip).toInt(); if (compiledMethod.hasBridgeFromNativeAnnotation()) { // skip native frames, stopping at last native frame preceeding the // Java To C transition frame fp = RuntimeEntrypoints.unwindNativeStackFrame(fp); } } } stackFrameCount++; ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); } VM.enableGC(); } /** * Walk the stack counting the number of stack frames encountered. * The stack being walked is our stack, so code is Uninterrupible to stop the * stack moving. * @return number of stack frames encountered */ @Uninterruptible @NoInline private int countFramesUninterruptible(RVMThread stackTraceThread) { int stackFrameCount = 0; Address fp; Address ip; /* Stack trace for the current thread */ fp = Magic.getFramePointer(); ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) { int compiledMethodId = Magic.getCompiledMethodID(fp); if (compiledMethodId != INVISIBLE_METHOD_ID) { 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++; ip = Magic.getReturnAddress(fp); 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. */ @Uninterruptible @NoInline private void recordFramesUninterruptible(RVMThread stackTraceThread) { int stackFrameCount = 0; Address fp; Address ip; /* Stack trace for the current thread */ fp = Magic.getFramePointer(); ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) { //VM.sysWriteln("at stackFrameCount = ",stackFrameCount); int compiledMethodId = Magic.getCompiledMethodID(fp); compiledMethods[stackFrameCount] = compiledMethodId; if (compiledMethodId != INVISIBLE_METHOD_ID) { 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); fp = Magic.getCallerFramePointer(fp); } } /** Class to wrap up a stack frame element */ public static class Element { /** Stack trace's method, null => invisible or trap */ private final RVMMethod method; /** Line number of element */ private final int lineNumber; /** Is this an invisible method? */ private final boolean isInvisible; /** Is this a hardware trap method? */ private final boolean isTrap; /** Constructor for non-opt compiled methods */ 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 */ Element(RVMMethod method, int ln) { this.method = method; lineNumber = ln; isTrap = false; isInvisible = false; } /** Get source file name */ public String getFileName() { if (isInvisible || isTrap) { return null; } else { Atom fn = method.getDeclaringClass().getSourceName(); return (fn != null) ? fn.toString() : null; } } /** Get class name */ public String getClassName() { if (isInvisible || isTrap) { return ""; } else { return method.getDeclaringClass().toString(); } } /** Get class */ public Class<?> getElementClass() { if (isInvisible || isTrap) { return null; } return method.getDeclaringClass().getClassForType(); } /** Get method name */ public String getMethodName() { if (isInvisible) { return "<invisible method>"; } else if (isTrap) { return "<hardware trap>"; } else { return method.getName().toString(); } } /** Get line number */ public int getLineNumber() { return lineNumber; } public boolean isNative() { if (isInvisible || isTrap) { return false; } else { return method.isNative(); } } } /** * Get the compiled method at element */ private CompiledMethod getCompiledMethod(int element) { if ((element >= 0) && (element < compiledMethods.length)) { int mid = compiledMethods[element]; if (mid != INVISIBLE_METHOD_ID) { return CompiledMethods.getCompiledMethod(mid); } } return null; } /** 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 = new Element[countFrames(first, last)]; if (!VM.BuildForOptCompiler) { int element = 0; for (int i=first; i <= last; i++) { elements[element] = new Element(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] = new Element(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] = new Element(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.getMemberRef(mid).asMethodReference().getResolvedMember(); int lineNumber = ((NormalMethod)method).getLineNumberForBCIndex(bci); elements[element] = new Element(method, lineNumber); 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 */ 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 assumig 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 while((element < compiledMethods.length) && (compiledMethod != null) && compiledMethod.getMethod().getDeclaringClass().getClassForType() == StackTrace.class) { 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().isThrowable()) { 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 hardware exception deliverer frames if (element < compiledMethods.length - 2) { compiledMethod = getCompiledMethod(element+1); if ((compiledMethod != null) && compiledMethod.getCompilerType() == CompiledMethod.TRAP) { element+=2; } } 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] == INVISIBLE_METHOD_ID) { // 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; } } }