/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.stack;
import static com.sun.max.platform.Platform.*;
import static com.sun.max.vm.compiler.deopt.Deoptimization.*;
import static com.sun.max.vm.compiler.target.Stub.*;
import static com.sun.max.vm.compiler.target.Stub.Type.*;
import static com.sun.max.vm.stack.StackFrameWalker.Purpose.*;
import static com.sun.max.vm.thread.VmThreadLocal.*;
import com.sun.max.annotate.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.jni.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.thread.*;
/**
* A walker that iterates over the frames in a thread's stack.
* <p>
* The walker manages two {@linkplain StackFrameCursor cursors} internally, each of which encapsulates
* the state needed about a stack frame: one for the <em>current</em>
* frame (caller frame) and one for the <em>previous</em> frame (callee frame).
* <p>
* The walker destructively updates the cursors' contents, rather than allocating new ones,
* since allocation is disallowed when walking the stack for reference map preparation.
*
* @see StackFrameCursor
*/
public abstract class StackFrameWalker {
/**
* A VM option for enabling stack frame walk tracing.
*/
public static boolean TraceStackWalk;
static {
VMOptions.addFieldOption("-XX:", "TraceStackWalk", "Trace every stack walk");
}
/**
* Constants denoting the finite set of reasons for which a stack walk can be performed.
*/
public enum Purpose {
/**
* Raising an exception.
* This type of stack walk is allocation free.
*/
EXCEPTION_HANDLING(StackUnwindingContext.class),
/**
* Preparing {@linkplain StackReferenceMapPreparer stack reference map} for a thread's stack.
* This type of stack walk is allocation free.
*/
REFERENCE_MAP_PREPARING(StackReferenceMapPreparer.class),
/**
* Reflecting on the frames of a thread's stack.
* This type of stack walk is allocation free.
*/
RAW_INSPECTING(RawStackFrameVisitor.class),
/**
* Reflecting on the frames of a thread's stack.
* This type of stack walk is not allocation free.
*/
INSPECTING(null);
private final Class contextType;
private Purpose(Class contextType) {
this.contextType = contextType;
}
/**
* Determines if a given context object is of the type expected by this purpose.
*/
public final boolean isValidContext(Object context) {
if (this == INSPECTING) {
return true;
}
return contextType.isInstance(context);
}
}
protected StackFrameCursor current;
protected StackFrameCursor callee;
private Purpose purpose = null;
private Pointer currentAnchor;
@HOSTED_ONLY
private StackFrame calleeStackFrame;
protected StackFrameWalker() {
// These initializations are overridden by the Inspector with variant cursor implementations.
// This is an awkward and fragile way to handle the override, but it avoids object initialization problems
// during construction of the Inspector's specialization of the walker.
this.current = new StackFrameCursor(this);
this.callee = new StackFrameCursor(this);
}
/**
* Walks a thread's stack.
* <p>
* Note that this method does not explicitly {@linkplain #reset() reset} this stack walker. If this walk is for the
* purpose of raising an exception, then the code that unwinds the stack to the exception handler frame is expected
* to reset this walker. For all other purposes, the caller of this method must reset this walker.
*
* @param ip the instruction pointer of the code executing in the top frame
* @param sp a pointer denoting an ISA defined location in the top frame
* @param fp a pointer denoting an ISA defined location in the top frame
* @param purpose the reason this walk is being performed
* @param context a purpose-specific object of a type {@linkplain Purpose#isValidContext(Object) compatible} with
* {@code purpose}
*/
private void walk(Pointer ip, Pointer sp, Pointer fp, Purpose purpose, Object context) {
checkPurpose(purpose, context);
current.reset();
callee.reset();
current.ip.derive(ip);
current.sp = sp;
current.fp = fp;
current.isTopFrame = true;
this.purpose = purpose;
this.currentAnchor = readPointer(LAST_JAVA_FRAME_ANCHOR);
boolean isTopFrame = true;
boolean initialIsInNative = !currentAnchor.isZero() && !readWord(currentAnchor, JavaFrameAnchor.PC.offset).isZero();
while (!current.sp.isZero()) {
TargetMethod tm = current.targetMethod();
TargetMethod calleeTM = callee.targetMethod();
traceCursor(current);
if (tm != null && (!initialIsInNative || (purpose == INSPECTING || purpose == RAW_INSPECTING))) {
// found target method
initialIsInNative = false;
checkVmEntrypointCaller(calleeTM, tm);
// walk the frame
if (!walkFrame(current, callee, tm, purpose, context)) {
break;
}
} else {
// did not find target method => in native code
if (MaxineVM.isHosted() && purpose == INSPECTING) {
final StackFrameVisitor stackFrameVisitor = (StackFrameVisitor) context;
if (!stackFrameVisitor.visitFrame(new NativeStackFrame(calleeStackFrame, current.nativeIP(), current.fp, current.sp))) {
break;
}
} else if (purpose == RAW_INSPECTING) {
final RawStackFrameVisitor stackFrameVisitor = (RawStackFrameVisitor) context;
current.isTopFrame = isTopFrame;
if (!stackFrameVisitor.visitFrame(current, callee)) {
break;
}
}
if (initialIsInNative) {
initialIsInNative = false;
Pointer anchor = nextNativeStubAnchor();
advanceFrameInNative(anchor, purpose);
} else {
if (calleeTM == null) {
// This is a native function that called a VM entry point such as VmThread.run(),
// MaxineVM.run() or a JNI function.
break;
}
ClassMethodActor lastJavaCalleeMethodActor = calleeTM.classMethodActor();
if (calleeTM.is(TrapStub)) {
Pointer anchor = nextNativeStubAnchor();
if (!anchor.isZero()) {
// This can only occur in the Inspector and implies that execution is in
// the trap stub as a result a trap or signal while execution was in
// native code.
advanceFrameInNative(anchor, purpose);
} else {
// This can only occur in the Inspector and implies that execution is in the trab stub
// before the point where the trap frame has been completed. In
// particular, the return instruction pointer slot has not been updated with the instruction
// pointer at which the fault occurred.
break;
}
} else if (lastJavaCalleeMethodActor != null && lastJavaCalleeMethodActor.isVmEntryPoint()) {
if (!advanceVmEntryPointFrame(calleeTM)) {
break;
}
} else if (lastJavaCalleeMethodActor == null) {
FatalError.unexpected("Unrecognized target method without a class method actor!");
} else {
Log.print("Native code called/entered a Java method that is not a JNI function, a Java trap stub or a VM/thread entry point: ");
Log.print(lastJavaCalleeMethodActor.name.string);
Log.print(lastJavaCalleeMethodActor.descriptor().string);
Log.print(" in ");
Log.println(lastJavaCalleeMethodActor.holder().name.string);
if (!FatalError.inFatalError()) {
FatalError.unexpected("Native code called/entered a Java method that is not a JNI function, a Java trap stub or a VM/thread entry point");
} else {
reset();
break;
}
}
}
}
isTopFrame = false;
}
}
private boolean walkFrame(StackFrameCursor current, StackFrameCursor callee, TargetMethod targetMethod, Purpose purpose, Object context) {
boolean proceed = true;
if (purpose == Purpose.REFERENCE_MAP_PREPARING) {
// walk the frame for reference map preparation
StackReferenceMapPreparer preparer = (StackReferenceMapPreparer) context;
if (preparer.checkIgnoreCurrentFrame()) {
proceed = true;
} else {
targetMethod.prepareReferenceMap(current, callee, preparer);
Pointer limit = preparer.completingReferenceMapLimit();
if (!limit.isZero() && current.sp().greaterEqual(limit)) {
proceed = false;
}
}
} else if (purpose == Purpose.EXCEPTION_HANDLING) {
// walk the frame for exception handling
Throwable throwable = ((StackUnwindingContext) context).throwable;
targetMethod.catchException(current, callee, throwable);
} else if (MaxineVM.isHosted() && purpose == Purpose.INSPECTING) {
// walk the frame for inspecting (Java frames)
StackFrameVisitor visitor = (StackFrameVisitor) context;
proceed = targetMethod.acceptStackFrameVisitor(current, visitor);
} else if (purpose == Purpose.RAW_INSPECTING) {
// walk the frame for inspect (compiled frames)
RawStackFrameVisitor visitor = (RawStackFrameVisitor) context;
proceed = visitor.visitFrame(current, callee);
}
// in any case, advance to the next frame
if (proceed) {
targetMethod.advance(current);
return true;
}
return false;
}
private void traceWalkPurpose(Purpose purpose) {
if (TraceStackWalk) {
Log.print("StackFrameWalk: Start stack frame walk for purpose ");
Log.println(purpose);
}
}
private void traceCursor(StackFrameCursor cursor) {
if (TraceStackWalk) {
if (cursor.targetMethod() != null) {
Log.print("StackFrameWalk: Frame at ");
Log.printLocation(cursor.targetMethod(), cursor.vmIP(), false);
Log.print(", isTopFrame=");
Log.print(cursor.isTopFrame);
Log.print(", sp=");
Log.print(cursor.sp);
Log.print(", fp=");
Log.print(cursor.fp);
Log.println("");
} else {
Log.print("StackFrameWalk: Frame for native function [IP=");
Log.print(cursor.nativeIP());
Log.println(']');
}
}
}
private void checkVmEntrypointCaller(TargetMethod lastJavaCallee, final TargetMethod targetMethod) {
if (lastJavaCallee != null && lastJavaCallee.classMethodActor() != null) {
final ClassMethodActor classMethodActor = lastJavaCallee.classMethodActor();
if (classMethodActor.isVmEntryPoint()) {
Log.print("Caller of VM entry point (@VM_ENTRY_POINT annotated method) \"");
Log.print(lastJavaCallee.regionName());
Log.print("\" is not native code: ");
Log.print(targetMethod.regionName());
Log.print(targetMethod.classMethodActor().descriptor().string);
Log.print(" in ");
Log.println(targetMethod.classMethodActor().holder().name.string);
FatalError.unexpected("Caller of a VM entry point (@VM_ENTRY_POINT method) must be native code");
}
}
}
/**
* Advances this stack walker through the frame of a method annotated with {@link VM_ENTRY_POINT}.
*
* @param lastJavaCallee the last java method called
* @return true if the stack walker was advanced
*/
private boolean advanceVmEntryPointFrame(TargetMethod lastJavaCallee) {
final ClassMethodActor lastJavaCalleeMethodActor = lastJavaCallee.classMethodActor();
if (lastJavaCalleeMethodActor != null && lastJavaCalleeMethodActor.isVmEntryPoint()) {
Pointer anchor = nextNativeStubAnchor();
if (anchor.isZero()) {
return false;
}
final Word lastJavaCallerInstructionPointer = readWord(anchor, JavaFrameAnchor.PC.offset);
final Word lastJavaCallerStackPointer = readWord(anchor, JavaFrameAnchor.SP.offset);
final Word lastJavaCallerFramePointer = readWord(anchor, JavaFrameAnchor.FP.offset);
boolean wasDisabled = SafepointPoll.disable();
advance(checkNativeFunctionCall(CodePointer.from(lastJavaCallerInstructionPointer), true).toPointer(),
lastJavaCallerStackPointer,
lastJavaCallerFramePointer);
if (!wasDisabled) {
SafepointPoll.enable();
}
return true;
}
return false;
}
/**
* Advances this walker past the first frame encountered when walking the stack of a thread that is executing
* in native code.
*/
private void advanceFrameInNative(Pointer anchor, Purpose purpose) {
FatalError.check(!anchor.isZero(), "No native stub frame anchor found when executing 'in native'");
CodePointer nativeFunctionCall = CodePointer.from(readWord(anchor, JavaFrameAnchor.PC.offset));
if (nativeFunctionCall.isZero()) {
FatalError.unexpected("Thread cannot be 'in native' without having recorded the last Java caller in thread locals");
}
CodePointer ip = checkNativeFunctionCall(nativeFunctionCall, purpose != INSPECTING && purpose != RAW_INSPECTING);
if (ip.isZero()) {
ip = nativeFunctionCall;
}
boolean wasDisabled = SafepointPoll.disable();
advance(ip.toPointer(), readWord(anchor, JavaFrameAnchor.SP.offset), readWord(anchor, JavaFrameAnchor.FP.offset));
if (!wasDisabled) {
SafepointPoll.enable();
}
}
/**
* Gets the next anchor in a VM exit frame. That is, get the frame in the next {@linkplain NativeStubGenerator native stub} on the stack
* above the current stack pointer.
*
* @return {@link Pointer#zero()} if there are no more native stub frame anchors
*/
private Pointer nextNativeStubAnchor() {
if (currentAnchor.isZero()) {
// We're at a VM entry point that has no Java frames above it.
return Pointer.zero();
}
// Skip over an anchor in a VM entry point frame. The test is the same as JavaFrameAnchor.inJava()
// except we can't use the latter here if in a separate address space (e.g. the Inspector) than the VM.
Pointer pc = readWord(currentAnchor, JavaFrameAnchor.PC.offset).asPointer();
if (pc.isZero()) {
currentAnchor = readWord(currentAnchor, JavaFrameAnchor.PREVIOUS.offset).asPointer();
if (currentAnchor.isZero()) {
// We're at a VM entry point that has no Java frames above it.
return Pointer.zero();
}
}
pc = readWord(currentAnchor, JavaFrameAnchor.PC.offset).asPointer();
if (pc.isZero()) {
// Java frame anchors should always alternate between VM entry and exit frames.
FatalError.unexpected("Found two adjacent VM entry point frame anchors");
}
Pointer anchor = this.currentAnchor;
this.currentAnchor = readWord(anchor, JavaFrameAnchor.PREVIOUS.offset).asPointer();
if (!anchor.isZero()) {
// Recurse if the anchor is below the current frame in the stack walk.
// This can occur when a stack walk is initiated further up the stack
// than the currently executing frame.
if (readWord(anchor, JavaFrameAnchor.SP.offset).asPointer().lessThan(current.sp)) {
return nextNativeStubAnchor();
}
}
return anchor;
}
private void checkPurpose(Purpose purpose, Object context) {
traceWalkPurpose(purpose);
if (!purpose.isValidContext(context)) {
FatalError.unexpected("Invalid stack walk context");
}
if (!current.sp.isZero()) {
Log.print("Stack walker already in use for ");
Log.println(this.purpose.name());
current.reset();
callee.reset();
this.purpose = null;
FatalError.unexpected("Stack walker already in use");
}
}
/**
* Checks the address of the native function call in a native method stub.
*
* @param pc the address of a native function call saved by {@link Snippets#nativeCallPrologue(NativeFunction)} or
* {@link Snippets#nativeCallPrologueForC(NativeFunction)}
* @param fatalIfNotFound specifies whether a fatal error should be raised if {@code pc} is not a native call site
* @return the value of {@code pc} if it is valid or zero if not
*/
private CodePointer checkNativeFunctionCall(CodePointer pc, boolean fatalIfNotFound) {
final TargetMethod nativeStub = targetMethodFor(pc.toPointer());
if (nativeStub != null) {
if (TraceStackWalk && purpose == Purpose.REFERENCE_MAP_PREPARING) {
final int nativeFunctionCallPos = nativeStub.posFor(pc);
Log.print("IP for stack frame preparation of stub for native method ");
Log.print(nativeStub.name());
Log.print(" [");
Log.print(nativeStub.codeStart());
Log.print("+");
Log.print(nativeFunctionCallPos);
Log.println(']');
}
return pc;
}
if (fatalIfNotFound) {
Log.print("Could not find native stub for instruction pointer ");
Log.println(pc);
throw FatalError.unexpected("Could not find native function call in native stub");
}
return CodePointer.zero();
}
/**
* Looks up a method based on a return address read from a frame. Depending on the architecture,
* the return address must be adjusted before searching the code cache to ensure the
* address within the method containing the call instruction.
*/
private TargetMethod targetMethodForReturnAddress(Word retAddr) {
if (platform().isa.offsetToReturnPC == 0) {
// Adjust 'retAddr' to ensure it is within the call instruction.
// This ensures we will always get the correct method, even if
// the call instruction was the last instruction in a method.
return targetMethodFor(retAddr.asPointer().minus(1));
} else {
return targetMethodFor(retAddr.asPointer());
}
}
/**
* Walks a thread's stack for the purpose of inspecting one or more frames on the stack. This method takes care of
* {@linkplain #reset() resetting} this walker before returning.
*
* This method is only ever used in Inspector contexts, hence the annotation with {@link HOSTED_ONLY}.
*/
@HOSTED_ONLY
public final void inspect(Pointer ip, Pointer sp, Pointer fp, final StackFrameVisitor visitor) {
// Wraps the visit operation to record the visited frame as the parent of the next frame to be visited.
final StackFrameVisitor wrapper = new StackFrameVisitor() {
@Override
public boolean visitFrame(StackFrame stackFrame) {
if (calleeStackFrame == null || !stackFrame.isSameFrame(calleeStackFrame)) {
calleeStackFrame = stackFrame;
} else {
Log.println("Same frame being visited twice: " + stackFrame);
}
return visitor.visitFrame(stackFrame);
}
};
walk(ip, sp, fp, INSPECTING, wrapper);
calleeStackFrame = null;
visitor.done();
reset();
}
/**
* Walks a thread's stack for the purpose of inspecting one or more frames on the stack. This method takes care of
* {@linkplain #reset() resetting} this walker before returning.
*/
public final void inspect(Pointer ip, Pointer sp, Pointer fp, final RawStackFrameVisitor visitor) {
walk(ip, sp, fp, RAW_INSPECTING, visitor);
visitor.done();
reset();
}
private final StackUnwindingContext defaultStackUnwindingContext = new StackUnwindingContext();
/**
* Walks a thread's stack for the purpose of raising an exception.
*/
public final void unwind(Pointer ip, Pointer sp, Pointer fp, Throwable throwable) {
defaultStackUnwindingContext.throwable = throwable;
defaultStackUnwindingContext.stackPointer = sp;
walk(ip, sp, fp, EXCEPTION_HANDLING, defaultStackUnwindingContext);
}
/**
* Walks a thread's stack for the purpose of preparing the reference map of a thread's stack. This method takes care of
* {@linkplain #reset() resetting} this walker before returning.
*/
public final void prepareReferenceMap(Pointer ip, Pointer sp, Pointer fp, StackReferenceMapPreparer preparer) {
walk(ip, sp, fp, REFERENCE_MAP_PREPARING, preparer);
reset();
}
/**
* Walks a thread's stack for the purpose of preparing the reference map of a thread's stack. This method takes care of
* {@linkplain #reset() resetting} this walker before returning.
*/
public final void verifyReferenceMap(Pointer ip, Pointer sp, Pointer fp, StackReferenceMapPreparer preparer) {
walk(ip, sp, fp, REFERENCE_MAP_PREPARING, preparer);
reset();
}
/**
* Terminates the current stack walk.
*/
public final void reset() {
if (TraceStackWalk) {
Log.print("StackFrameWalk: Finish stack frame walk for purpose ");
Log.println(purpose);
}
current.reset();
callee.reset();
purpose = null;
defaultStackUnwindingContext.stackPointer = Pointer.zero();
defaultStackUnwindingContext.throwable = null;
}
/**
* Gets the last stack frame {@linkplain StackFrameVisitor#visitFrame(StackFrame) visited} by this stack walker
* while {@linkplain #inspect(Pointer, Pointer, Pointer, StackFrameVisitor) inspecting}. The returned frame is
* the callee frame of the next frame to be visited.
*/
@HOSTED_ONLY
public final StackFrame calleeStackFrame() {
return calleeStackFrame;
}
/**
* Determines if this stack walker is currently in use. This is useful for detecting if an exception is being thrown as part of exception handling.
*/
public final boolean isInUse() {
return !current.sp.isZero();
}
/**
* Advances this stack walker such that {@link #current} becomes {@link #callee}.
*
* @param retAddr the instruction pointer of the new current frame (return address read from the current frame)
* @param sp the stack pointer of the new current frame (stack pointer in the caller frame)
* @param fp the frame pointer of the new current frame
*/
public final void advance(Word retAddr, Word sp, Word fp) {
callee.copyFrom(current);
Pointer ip = retAddr.asPointer();
TargetMethod tm = targetMethodForReturnAddress(retAddr);
// Rescue a return address that has been patched for deoptimization
if (isDeoptStubEntry(ip, tm)) {
// Since 'ip' denotes the start of a deopt stub, then we're dealing with a patched return address
// and the real caller is found in the rescue slot
Pointer originalReturnAddress = readWord(sp.asAddress(), DEOPT_RETURN_ADDRESS_OFFSET).asPointer();
tm = targetMethodForReturnAddress(originalReturnAddress);
ip = originalReturnAddress;
}
// distinguish between a native function and a target method
int pos = 0;
if (tm != null) {
// target method
pos = tm.posFor(CodePointer.from(ip));
ip = Pointer.zero();
} else {
// native function
}
current.advance(tm, pos, ip, sp.asPointer(), fp.asPointer());
}
public abstract TargetMethod targetMethodFor(Pointer instructionPointer);
public abstract Word readWord(Address address, int offset);
public abstract byte readByte(Address address, int offset);
public abstract int readInt(Address address, int offset);
/**
* Reads the value of a given VM thread local from the safepoint-enabled thread locals.
*
* @param tl the VM thread local to read
* @return the value (as a pointer) of {@code local} in the safepoint-enabled thread locals
*/
public abstract Pointer readPointer(VmThreadLocal tl);
}