/*
* 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.runtime;
import static com.sun.max.vm.MaxineVM.*;
import static com.sun.max.vm.VMOptions.*;
import static com.sun.max.vm.compiler.deopt.Deoptimization.*;
import static com.sun.max.vm.runtime.Trap.Number.*;
import static com.sun.max.vm.thread.VmThread.*;
import static com.sun.max.vm.thread.VmThreadLocal.*;
import com.sun.max.annotate.*;
import com.sun.max.lang.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.code.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.thread.*;
/**
* This class handles synchronous operating systems signals used to implement the following VM features:
* <ul>
* <li>safepoints</li>
* <li>runtime exceptions: {@link NullPointerException}, {@link ArithmeticException}, {@link StackOverflowError}</li>
* <li>de-opt</li>
* </ul>
* The execution path from an OS signal to the {@linkplain Stubs#trapStub trap stub} is as follows:
* <ol>
* <li>A native handler is notified of the signal (see 'vmSignalHandler' in trap.c)</li>
* <li>The native handler analyzes the context of the signal to detect stack-overflow.</li>
* <li>The native handler writes trap {@linkplain VmThreadLocal#TRAP_NUMBER number},
* {@linkplain VmThreadLocal#TRAP_FAULT_ADDRESS address} and
* {@link VmThreadLocal#TRAP_INSTRUCTION_POINTER pc} into thread locals.</li>
* <li>The native handler disables safepoints by modifying the register context of the
* trap in (almost) the same way as {@link SafepointPoll#disable()}.</li>
* <li>The native handler modifies the instruction pointer in the trap context to point to the
* entry point of the trap stub.</li>
* <li>The native handler returns which effects a jump to the trap stub in the frame of
* the trapped method/function.</li>
* </ol>
*/
public abstract class Trap {
/**
* The numeric identifiers for the traps that can be handled by the VM. Note that these do not correspond with the
* native signals.
*
* The values defined here (except for {@link #NULL_POINTER_EXCEPTION} and {@link #SAFEPOINT}) must correspond to
* those of the same name defined in Native/substrate/trap.c.
*
* The {@link #NULL_POINTER_EXCEPTION} and {@link #SAFEPOINT} values are used in
* {@link Trap#handleMemoryFault(CodePointer, TargetMethod, Pointer, Pointer, Pointer, Address)} to disambiguate a memory fault.
*/
public static final class Number {
public static final int MEMORY_FAULT = 0;
public static final int STACK_FAULT = 1;
public static final int STACK_FATAL = 2;
public static final int ARITHMETIC_EXCEPTION = 3;
public static final int ASYNC_INTERRUPT = 4;
public static final int NULL_POINTER_EXCEPTION = 5;
public static final int SAFEPOINT = 6;
public static String toExceptionName(int trapNumber) {
switch (trapNumber) {
case MEMORY_FAULT:
return "MEMORY_FAULT";
case STACK_FAULT:
return "STACK_FAULT";
case ARITHMETIC_EXCEPTION:
return "ARITHMETIC_EXCEPTION";
case ASYNC_INTERRUPT:
return "ASYNC_INTERRUPT";
case NULL_POINTER_EXCEPTION:
return "NULL_POINTER_EXCEPTION";
case SAFEPOINT:
return "SAFEPOINT";
default:
return "unknown";
}
}
public static boolean isImplicitException(int trapNumber) {
return trapNumber == ARITHMETIC_EXCEPTION || trapNumber == NULL_POINTER_EXCEPTION || trapNumber == STACK_FAULT || trapNumber == STACK_FATAL;
}
public static Class<? extends Throwable> toImplicitExceptionClass(int trapNumber) {
if (trapNumber == ARITHMETIC_EXCEPTION) {
return ArithmeticException.class;
} else if (trapNumber == NULL_POINTER_EXCEPTION) {
return NullPointerException.class;
} else if (trapNumber == STACK_FAULT || trapNumber == STACK_FATAL) {
return StackOverflowError.class;
}
return null;
}
private Number() {
}
public static boolean isStackOverflow(Pointer trapFrame) {
return vm().trapFrameAccess.getTrapNumber(trapFrame) == STACK_FAULT;
}
}
private static boolean DumpStackOnTrap;
static {
VMOptions.addFieldOption("-XX:", "DumpStackOnTrap", Trap.class, "Reports a stack trace for every trap, regardless of the cause.", MaxineVM.Phase.PRISTINE);
}
/**
* Whether to bang on the stack in the method prologue.
*/
public static final boolean STACK_BANGING = true;
/**
* The number of bytes reserved in the stack as a guard area.
* Note that SPARC code is more efficient if this is set below 6K. Specifically, set to (6K - 1 - typical_frame_size).
*/
public static final int stackGuardSize = 12 * Ints.K;
/**
* Handle to {@link #handleTrap(int, Pointer, Address)}.
*/
public static final CriticalMethod handleTrap = new CriticalMethod(Trap.class, "handleTrap", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT);
@HOSTED_ONLY
protected Trap() {
}
/**
* A VM option to enable tracing of traps, both in the C and Java parts of trap handling.
*/
private static VMBooleanXXOption TraceTrapsOption = register(new VMBooleanXXOption("-XX:-TraceTraps", "Trace traps.") {
@Override
public boolean parseValue(Pointer optionValue) {
TraceTraps = TraceTrapsOption.getValue();
nativeSetTrapTracing(TraceTraps);
return true;
}
}, MaxineVM.Phase.PRISTINE);
private static boolean TraceTraps = TraceTrapsOption.getValue();
/**
* Initializes the native side of trap handling by informing the C code of the address of {@link Stubs#trapStub}.
*
* @param vmTrapHandler the entry point of {@link Stubs#trapStub}
*/
@C_FUNCTION
private static native void nativeTrapInitialize(Word vmTrapHandler);
/**
* Updates the tracing flag for traps in the native substrate.
*/
@C_FUNCTION // called on primordial thread
private static native void nativeSetTrapTracing(boolean flag);
/**
* Installs the trap handlers using the operating system's API.
*/
public static void initialize() {
nativeTrapInitialize(vm().stubs.trapStub().codeStart().toAddress());
nativeSetTrapTracing(TraceTraps);
}
/**
* This method is called from the {@linkplain Stubs#trapStub trap stub} and does the actual trap handling.
*
* @param trapNumber the trap that occurred
* @param trapFrame the trap frame
* @param faultAddress the faulting address that caused this trap (memory faults only)
*/
private static void handleTrap(int trapNumber, Pointer trapFrame, Address faultAddress) {
// From this point on until we return from the trap stub,
// this variable is used to communicate to the VM operation thread
// whether a thread was stopped at a safepoint or in native code
TRAP_INSTRUCTION_POINTER.store3(Pointer.zero());
if (trapNumber == ASYNC_INTERRUPT) {
VmThread.current().setInterrupted();
return;
}
final TrapFrameAccess tfa = vm().trapFrameAccess;
final Pointer pc = tfa.getPC(trapFrame);
final Object origin = checkTrapOrigin(trapNumber, trapFrame, faultAddress, pc);
if (origin instanceof TargetMethod) {
// the trap occurred in Java
final TargetMethod targetMethod = (TargetMethod) origin;
final Pointer sp = tfa.getSP(trapFrame);
final Pointer fp = tfa.getFP(trapFrame);
CodePointer vmIP = CodePointer.from(pc);
switch (trapNumber) {
case MEMORY_FAULT:
handleMemoryFault(vmIP, targetMethod, sp, fp, trapFrame, faultAddress);
break;
case STACK_FAULT:
// stack overflow:
// the native trap handler unprotected the yellow zone -
// propagate this to the thread object
VmThread.current().nativeTrapHandlerUnprotectedYellowZone();
raiseImplicitException(trapFrame, targetMethod, StackOverflowError.class, sp, fp, vmIP);
break; // unreachable, except when returning to a local exception handler
case ARITHMETIC_EXCEPTION:
// integer divide by zero
raiseImplicitException(trapFrame, targetMethod, ArithmeticException.class, sp, fp, vmIP);
break; // unreachable
case STACK_FATAL:
// fatal stack overflow
FatalError.unexpected("fatal stack fault in red zone", false, null, trapFrame);
break; // unreachable
default:
Log.print("Unhandled trap in target method");
Log.printMethod(targetMethod, false);
Log.print("@ ");
Log.print(pc);
Log.print(" trap #");
Log.println(trapNumber);
FatalError.unexpected("unknown trap number", false, null, trapFrame);
}
} else {
// the fault occurred in native code
Log.print("Trap in native code (or a runtime stub) @ ");
Log.print(pc);
Log.print(" trap #");
Log.print(trapNumber);
Log.println(", exiting.");
FatalError.unexpected("Trap in native code or a runtime stub", true, null, trapFrame);
}
}
/**
* Checks the origin of a trap by looking for a target method or runtime stub in the code regions. If found, this
* method will return a reference to the {@code TargetMethod} that produced the trap. If a runtime stub produced
* the trap, this method will return a reference to that runtime stub. Otherwise, this method returns {@code null},
* indicating the trap occurred in native code.
*
* @param trapNumber the trap number
* @param trapFrame the trap frame
* @param faultAddress the faulting address that caused the trap (memory faults only)
* @param pc the address of instruction causing the trap
* @return a reference to the {@code TargetMethod} containing the instruction pointer that
* caused the trap or {@code null} if trap occurred in native code
*/
private static Object checkTrapOrigin(int trapNumber, Pointer trapFrame, Address faultAddress, Pointer pc) {
final TrapFrameAccess tfa = vm().trapFrameAccess;
// check to see if this fault originated in a target method
final TargetMethod targetMethod = Code.codePointerToTargetMethod(pc);
if (TraceTraps || DumpStackOnTrap) {
final boolean lockDisabledSafepoints = Log.lock();
Log.printCurrentThread(false);
if (targetMethod != null) {
Log.print(": Trapped at ");
Log.printLocation(targetMethod, CodePointer.from(pc), true);
} else {
Log.println(": Trapped in <unknown>");
}
Log.print(" Trap number=");
Log.println(trapNumber);
Log.print(" Instruction pointer=");
Log.println(pc);
Log.print(" Fault address=");
Log.println(faultAddress);
tfa.logTrapFrame(trapFrame);
if (DumpStackOnTrap) {
Throw.stackDump("Stack trace:", pc, tfa.getSP(trapFrame), tfa.getFP(trapFrame));
}
Log.unlock(lockDisabledSafepoints);
}
if (targetMethod != null) {
return targetMethod;
}
// this fault occurred in native code
return null;
}
/**
* Handle a memory fault for this thread. A memory fault can be caused by an implicit null pointer check,
* a safepoint being triggered, or a segmentation fault in native code.
*
* @param instructionPointer the instruction pointer that caused the fault
* @param targetMethod the TargetMethod containing {@code instructionPointer}
* @param stackPointer the stack pointer at the time of the fault
* @param framePointer the frame pointer at the time of the fault
* @param trapFrame a pointer to the trap frame
* @param faultAddress the address that caused the fault
*/
private static void handleMemoryFault(CodePointer instructionPointer, TargetMethod targetMethod, Pointer stackPointer, Pointer framePointer, Pointer trapFrame, Address faultAddress) {
final Pointer dtla = currentTLA();
final SafepointPoll safepoint = vm().safepointPoll;
final TrapFrameAccess tfa = vm().trapFrameAccess;
final Pointer ttla = TTLA.load(dtla);
final Pointer safepointLatch = tfa.getSafepointLatch(trapFrame);
if (VmThread.current().isVmOperationThread()) {
FatalError.unexpected("Memory fault on the VM operation thread", false, null, trapFrame);
}
// check to see if a safepoint has been triggered for this thread
if (safepointLatch.equals(ttla) && safepoint.isAt(instructionPointer)) {
// a safepoint has been triggered for this thread
final Pointer etla = ETLA.load(dtla);
final Reference reference = VM_OPERATION.loadRef(etla);
final VmOperation vmOperation = (VmOperation) reference.toJava();
tfa.setTrapNumber(trapFrame, Number.SAFEPOINT);
if (vmOperation != null) {
TRAP_INSTRUCTION_POINTER.store3(instructionPointer.toAddress());
vmOperation.doAtSafepoint(trapFrame);
while (VmOperation.isSuspendRequest(etla)) {
VmThread.fromTLA(etla).suspendMonitor.suspend();
// We must re-check the state because it is possible
// that even though we were resumed, we may have remained
// off CPU through another suspend operation.
}
TRAP_INSTRUCTION_POINTER.store3(Pointer.zero());
} else {
/*
* The interleaving of a mutator thread and a freezer thread below demonstrates
* one case where this can occur:
*
* Mutator thread | VmOperationThread
* ------------------------+-----------------------------------------------------------------
* | set VM_OPERATION and trigger safepoints for mutator thread
* loop: safepoint |
* enter native |
* | complete operation (e.g. GC)
* | reset safepoints and clear VM_OPERATION for mutator thread
* return from native|
* loop: safepoint |
*
* The first safepoint instruction above loads the address of triggered VM thread locals
* into the latch register. The second safepoint instruction dereferences the latch
* register causing the trap. That is, the VM operation thread triggered safepoints in the
* mutator to freeze but actually froze it as a result of the mutator making a
* native call between 2 safepoint instructions (it takes 2 executions of a safepoint
* instruction to cause the trap).
*
* The second safepoint instruction on the mutator thread will cause a trap when
* VM_OPERATION for the mutator is null.
*/
}
// The state of the safepoint latch was TRIGGERED when the trap happened. It must be reset back to ENABLED
// here otherwise another trap will occur as soon as the trap stub returns and re-executes the
// safepoint instruction.
tfa.setSafepointLatch(trapFrame, etla);
} else if (inJava(dtla)) {
tfa.setTrapNumber(trapFrame, Number.NULL_POINTER_EXCEPTION);
// null pointer exception
raiseImplicitException(trapFrame, targetMethod, NullPointerException.class, stackPointer, framePointer, instructionPointer);
} else {
// segmentation fault happened in native code somewhere, die.
FatalError.unexpected("Trap in native code", true, null, trapFrame);
}
}
public static boolean DeoptOnImplicitException;
static {
VMOptions.addFieldOption("-XX:", "DeoptOnImplicitException", "Deoptimize on implicit exception occuring in optimized code.");
}
/**
* Raises an implicit exception.
*
* If there is a local handler for the exception (i.e. a handler in the same frame in which the exception occurred)
* and the method in which the exception occurred was compiled by the opto compiler, then the trap state is altered
* so that the return address for the trap frame is set to be the exception handler entry address.
* This means that the register allocator can assume that registers are not modified in the control flow
* from an implicit exception to the exception handler.
*
* Otherwise, the {@linkplain Throw#raise(Throwable, Pointer, Pointer, CodePointer) standard mechanism} for throwing an
* exception is used.
*
* @param trapFrame a pointer to the trap frame
* @param tm the target method containing the trap address
* @param throwableClass the throwable class to instantiate and raise
* @param sp the stack pointer at the time of the trap
* @param fp the frame pointer at the time of the trap
* @param ip the instruction pointer which caused the trap
*/
private static void raiseImplicitException(Pointer trapFrame, TargetMethod tm, Class<? extends Throwable> throwableClass, Pointer sp, Pointer fp, CodePointer ip) {
if (DeoptOnImplicitException && !tm.isBaseline()) {
Stub stub = vm().stubs.deoptStubForSafepointPoll();
CodePointer to = stub.codeStart();
final TrapFrameAccess tfa = vm().trapFrameAccess;
Pointer save = tfa.getSP(trapFrame).plus(DEOPT_RETURN_ADDRESS_OFFSET);
Pointer patch = tfa.getPCPointer(trapFrame);
CodePointer from = CodePointer.from(patch.readWord(0));
assert !to.equals(from);
if (deoptLogger.enabled()) {
deoptLogger.logPatchReturnAddress(tm, "TRAP STUB", stub, to, save, patch, from);
}
patch.writeWord(0, to.toAddress());
save.writeWord(0, from.toAddress());
return;
}
Throwable throwable = null;
if (throwableClass == NullPointerException.class) {
throwable = new NullPointerException();
} else if (throwableClass == ArithmeticException.class) {
throwable = new ArithmeticException();
} else if (throwableClass == StackOverflowError.class) {
throwable = new StackOverflowError();
} else {
throw FatalError.unexpected("illegal implicit exception class");
}
Throw.traceThrow(throwable);
assert tm.invalidated() == null : "invalidated methods should not be executing";
if (tm.preserveRegistersForLocalExceptionHandler()) {
final CodePointer catchAddress = tm.throwAddressToCatchAddress(ip, throwable);
if (!catchAddress.isZero()) {
// Store the exception so that the handler can find it.
VmThread.current().storeExceptionForHandler(throwable, tm, tm.posFor(catchAddress));
final TrapFrameAccess tfa = vm().trapFrameAccess;
tfa.setPC(trapFrame, catchAddress.toPointer());
return;
}
}
Throw.raise(throwable, sp, fp, ip);
}
}