/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* 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/cpl1.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.VM_StackframeLayoutConstants.INVISIBLE_METHOD_ID;
import static org.jikesrvm.ArchitectureSpecific.VM_StackframeLayoutConstants.STACKFRAME_SENTINEL_FP;
import org.jikesrvm.VM;
import org.jikesrvm.VM_Options;
import org.jikesrvm.classloader.VM_Atom;
import org.jikesrvm.classloader.VM_MemberReference;
import org.jikesrvm.classloader.VM_Method;
import org.jikesrvm.classloader.VM_NormalMethod;
import org.jikesrvm.compilers.common.VM_CompiledMethod;
import org.jikesrvm.compilers.common.VM_CompiledMethods;
import org.jikesrvm.compilers.opt.VM_OptCompiledMethod;
import org.jikesrvm.compilers.opt.VM_OptEncodedCallSiteTree;
import org.jikesrvm.compilers.opt.VM_OptMachineCodeMap;
import org.jikesrvm.scheduler.VM_Scheduler;
import org.jikesrvm.scheduler.VM_Thread;
import org.vmmagic.unboxed.LocalAddress;
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 VM_StackTrace {
/**
* The compiled methods 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 VM_CompiledMethod[] compiledMethods;
/** The offset of the instruction within the compiled method */
private final int[] instructionOffsets;
/** Index of the last stack trace */
private static int lastTraceIndex = 0;
/** Index of this stack trace */
private final int traceIndex;
/** Should this be (or is this) a verbose stack trace? */
private boolean isVerbose() {
// If we're printing verbose stack traces...
// AND this particular trace meets the periodicity requirements
return (VM.VerboseStackTracePeriod > 0) &&
(((traceIndex - 1) % VM.VerboseStackTracePeriod) == 0);
}
/**
* Create a trace of the current call stack
*/
public VM_StackTrace() {
// Poor man's atomic integer, to get through bootstrap
synchronized(VM_StackTrace.class) {
lastTraceIndex++;
traceIndex = lastTraceIndex;
}
// (1) Count the number of frames comprising the stack.
int numFrames = walkFrames(false);
// (2) Construct arrays to hold raw data
compiledMethods = new VM_CompiledMethod[numFrames];
instructionOffsets = new int[numFrames];
// (3) Fill in arrays
walkFrames(true);
// Debugging trick: print every nth stack trace created
if (isVerbose()) {
VM.disableGC();
VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of creating VM_StackTrace # ", traceIndex);
VM_Scheduler.dumpStack();
VM.sysWriteln("END Verbosely dumping stack at time of creating VM_StackTrace # ", traceIndex, " ]");
VM.enableGC();
}
}
/**
* Walk the stack counting the number of stack frames encountered
* @param record fill in the compiledMethods and instructionOffsets arrays?
* @return number of stack frames encountered
*/
private int walkFrames(boolean record) {
int stackFrameCount = 0;
VM.disableGC(); // so fp & ip don't change under our feet
VM_Thread stackTraceThread = VM_Scheduler.getCurrentThread().getThreadForStackTrace();
LocalAddress fp;
LocalAddress ip;
if (stackTraceThread != VM_Scheduler.getCurrentThread()) {
/* Stack trace for a sleeping thread */
fp = stackTraceThread.contextRegisters.getInnermostFramePointer();
ip = stackTraceThread.contextRegisters.getInnermostInstructionAddress();
} else {
/* Stack trace for the current thread */
fp = VM_Magic.getFramePointer();
ip = VM_Magic.getReturnAddress(fp);
fp = VM_Magic.getCallerFramePointer(fp);
}
while (VM_Magic.getCallerFramePointer(fp).NE(VM_Magic.addressAsLocalAddress(STACKFRAME_SENTINEL_FP))) {
int compiledMethodId = VM_Magic.getCompiledMethodID(fp);
if (compiledMethodId != INVISIBLE_METHOD_ID) {
VM_CompiledMethod compiledMethod =
VM_CompiledMethods.getCompiledMethod(compiledMethodId);
if (record) {
compiledMethods[stackFrameCount] = compiledMethod;
}
if (compiledMethod.getCompilerType() != VM_CompiledMethod.TRAP) {
if (record) {
instructionOffsets[stackFrameCount] =
compiledMethod.getInstructionOffset(ip).toInt();
}
if (compiledMethod.getMethod().getDeclaringClass()
.hasBridgeFromNativeAnnotation()) {
// skip native frames, stopping at last native frame preceeding the
// Java To C transition frame
fp = VM_Runtime.unwindNativeStackFrame(fp);
}
}
}
stackFrameCount++;
ip = VM_Magic.getReturnAddress(fp);
fp = VM_Magic.getCallerFramePointer(fp);
}
VM.enableGC();
return stackFrameCount;
}
/** Class to wrap up a stack frame element */
public static class Element {
/** Stack trace's method, null => invisible or trap */
private final VM_Method 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(VM_CompiledMethod cm, int off) {
isInvisible = (cm == null);
if (!isInvisible) {
isTrap = cm.getCompilerType() == VM_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(VM_Method 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 {
VM_Atom fn = method.getDeclaringClass().getSourceName();
return (fn != null) ? fn.toString() : null;
}
}
/** Get class name */
public String getClassName() {
if (isInvisible || isTrap) {
return null;
} else {
return method.getDeclaringClass().toString();
}
}
/** 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();
}
}
}
/** 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(compiledMethods[i], instructionOffsets[i]);
element++;
}
} else {
int element = 0;
for (int i=first; i <= last; i++) {
VM_CompiledMethod compiledMethod = compiledMethods[i];
if ((compiledMethod == null) ||
(compiledMethod.getCompilerType() != VM_CompiledMethod.OPT)) {
// Invisible or non-opt compiled method
elements[element] = new Element(compiledMethod, instructionOffsets[i]);
element++;
} else {
Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]);
VM_OptCompiledMethod optInfo = (VM_OptCompiledMethod)compiledMethod;
VM_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 = VM_OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) {
int mid = VM_OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding);
VM_Method method = VM_MemberReference.getMemberRef(mid).asMethodReference().getResolvedMember();
int lineNumber = ((VM_NormalMethod)method).getLineNumberForBCIndex(bci);
elements[element] = new Element(method, lineNumber);
element++;
}
}
}
}
}
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++) {
VM_CompiledMethod compiledMethod = compiledMethods[i];
if ((compiledMethod == null) ||
(compiledMethod.getCompilerType() != VM_CompiledMethod.OPT)) {
// Invisible or non-opt compiled method
numElements++;
} else {
Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]);
VM_OptCompiledMethod optInfo = (VM_OptCompiledMethod)compiledMethod;
VM_OptMachineCodeMap map = optInfo.getMCMap();
int iei = map.getInlineEncodingForMCOffset(instructionOffset);
if (iei < 0) {
numElements++;
} else {
int[] inlineEncoding = map.inlineEncoding;
for (; iei >= 0; iei = VM_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.VM_StackTrace.<init>(VM_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.VM_Runtime.deliverHardwareException(VM_Runtime.java:682)
* at <hardware trap>(Unknown Source:0)
*
* and a software trap to look like:
* at org.jikesrvm.runtime.VM_StackTrace.<init>(VM_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.scheduler.VM_Processor.dispatch(VM_Processor.java:211)
* at org.jikesrvm.scheduler.VM_Thread.morph(VM_Thread.java:1125)
* ...
* at org.jikesrvm.memorymanagers.mminterface.MM_Interface.allocateSpace(MM_Interface.java:613)
* ...
* at org.jikesrvm.runtime.VM_Runtime.unresolvedNewArray(VM_Runtime.java:401)
*/
if (VM_Options.stackTraceFull) {
return 0;
} else {
int element = 0;
// Deal with OutOfMemoryError
if (cause instanceof OutOfMemoryError) {
// (1) search until VM_Runtime
while((element < compiledMethods.length) &&
(compiledMethods[element] != null) &&
compiledMethods[element].method.getDeclaringClass().getClassForType() != VM_Runtime.class) {
element++;
}
// (2) continue until not VM_Runtime
while((element < compiledMethods.length) &&
(compiledMethods[element] != null) &&
compiledMethods[element].method.getDeclaringClass().getClassForType() == VM_Runtime.class) {
element++;
}
return element;
}
// (1) remove any VM_StackTrace frames
while((element < compiledMethods.length) &&
(compiledMethods[element] != null) &&
compiledMethods[element].method.getDeclaringClass().getClassForType() == VM_StackTrace.class) {
element++;
}
// (2) remove any VMThrowable frames
while((element < compiledMethods.length) &&
(compiledMethods[element] != null) &&
compiledMethods[element].method.getDeclaringClass().getClassForType() == java.lang.VMThrowable.class) {
element++;
}
// (3) remove any Throwable frames
while((element < compiledMethods.length) &&
(compiledMethods[element] != null) &&
compiledMethods[element].method.getDeclaringClass().getClassForType() == java.lang.Throwable.class) {
element++;
}
// (4) remove frames belonging to exception constructors upto the causes constructor
while((element < compiledMethods.length) &&
(compiledMethods[element] != null) &&
(compiledMethods[element].method.getDeclaringClass().getClassForType() != cause.getClass()) &&
compiledMethods[element].method.isObjectInitializer() &&
compiledMethods[element].method.getDeclaringClass().isThrowable()) {
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) &&
(compiledMethods[element] != null) &&
(compiledMethods[element].method.getDeclaringClass().getClassForType() == cause.getClass()) &&
compiledMethods[element].method.isObjectInitializer()) {
element++;
}
// (6) remove possible hardware exception deliverer frames
if (element < compiledMethods.length - 2) {
if ((compiledMethods[element+1] != null) &&
compiledMethods[element+1].getCompilerType() == VM_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.VM_Reflection.invoke(VM_Reflection.java:132)
* at org.jikesrvm.scheduler.VM_MainThread.run(VM_MainThread.java:195)
* at org.jikesrvm.scheduler.VM_Thread.run(VM_Thread.java:534)
* at org.jikesrvm.scheduler.VM_Thread.startoff(VM_Thread.java:1113
*
* and on another thread to look like:
* at org.jikesrvm.scheduler.VM_Thread.run(VM_Thread.java:534)
* at org.jikesrvm.scheduler.VM_Thread.startoff(VM_Thread.java:1113)
*/
int max = compiledMethods.length-1;
if (VM_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] == null) {
// 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
}
}
if (compiledMethods[i].getCompilerType() == VM_CompiledMethod.TRAP) {
// looks like we've gone too low
return max;
}
Class<?> frameClass = compiledMethods[i].method.getDeclaringClass().getClassForType();
if ((frameClass != org.jikesrvm.scheduler.VM_MainThread.class) &&
(frameClass != org.jikesrvm.scheduler.VM_Thread.class) &&
(frameClass != org.jikesrvm.runtime.VM_Reflection.class)){
// Found a non-VM method
return i;
}
}
// No frame found
return max;
}
}
}