/* * Copyright (c) 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.ext.jvmti; import static com.sun.max.vm.ext.jvmti.JVMTIEvents.*; import com.sun.max.annotate.*; import com.sun.max.program.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.ext.jvmti.JVMTIThreadFunctions.*; import com.sun.max.vm.hosted.*; import com.sun.max.vm.jni.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.stack.*; import com.sun.max.vm.thread.*; import com.sun.max.vm.type.*; public class JVMTIException { /** * For communicating the data through to {@link JVMTI#event(int, Object)}. */ static class ExceptionEventData { Throwable throwable; MethodID methodID; int location; MethodID catchMethodID; int catchLocation; } static class EventState { ExceptionEventData exceptionEventData = new ExceptionEventData(); StackAnalyzer stackAnalyser = new StackAnalyzer(); VmStackFrameWalker sfw = new VmStackFrameWalker(VmThread.currentTLA()); boolean inProcess; } static class EventStateThreadLocal extends ThreadLocal<EventState> { @Override public EventState initialValue() { return new EventState(); } } /** * A pre-thread value that holds the state necessary to analyze and dispatch and exception event (and it's catch if any). */ private static EventStateThreadLocal eventStateTL = new EventStateThreadLocal(); /** * VM implementation methods that may be on the stack above the method that actually threw the exception. * This is rather ad hoc and compiler dependent, unfortunately. */ private static final MethodActor[] throwImplMethods = new ClassMethodActor[5]; @CONSTANT_WHEN_NOT_ZERO private static ClassActor throwClassActor; @HOSTED_ONLY private static class InitializationCompleteCallback implements JavaPrototype.InitializationCompleteCallback { @Override public void initializationComplete() { try { throwClassActor = ClassActor.fromJava(Throw.class); int i = 0; throwImplMethods[i++] = MethodActor.fromJava(VmThread.class.getDeclaredMethod("throwJniException")); Class<?> maxRuntimeCalls = Class.forName("com.oracle.max.vm.ext.maxri.MaxRuntimeCalls"); throwImplMethods[i++] = MethodActor.fromJava(maxRuntimeCalls.getDeclaredMethod("runtimeHandleException", Throwable.class)); throwImplMethods[i++] = MethodActor.fromJava(maxRuntimeCalls.getDeclaredMethod("runtimeUnwindException", Throwable.class)); } catch (Exception ex) { ProgramError.unexpected(ex); } } } static { JavaPrototype.registerInitializationCompleteCallback(new InitializationCompleteCallback()); } /** * A variant of {@link FindAppFramesStackTraceVisitor} that locates the * actual frame that threw the exception and whether or not the exception was * caught. Owing to the meta-circular nature of Maxine, neither question * is entirely trivial to answer. * */ private static class StackAnalyzer extends FindAppFramesStackTraceVisitor { Throwable throwable; ClassMethodActor throwingMethodActor; TargetMethod catchTargetMethod; int stackElementSizeAtCatch; int throwingBci = -1; long throwingFrameId; TargetMethod.CatchExceptionInfo catchInfo = new TargetMethod.CatchExceptionInfo(); @Override public boolean visitSourceFrame(ClassMethodActor methodActor, int bci, boolean trapped, long frameId) { if (throwingMethodActor == null) { ClassMethodActor methodActorOriginal = methodActor.original(); if (!(methodActorOriginal.holder() == throwClassActor || isThrowImplMethod(methodActor))) { this.throwingMethodActor = methodActorOriginal; this.throwingBci = bci; this.throwingFrameId = frameId; } } return super.visitSourceFrame(methodActor, bci, trapped, frameId); } private boolean isThrowImplMethod(ClassMethodActor methodActor) { for (int i = 0; i < throwImplMethods.length; i++) { if (methodActor == throwImplMethods[i]) { return true; } } return false; } @Override public boolean visitFrame(StackFrameCursor current, StackFrameCursor callee) { // raw frame visit with the info we need to check for a catch of throwable if (catchTargetMethod == null) { TargetMethod tm = current.targetMethod(); if (tm != null) { boolean isCaught = tm.catchExceptionInfo(current, throwable, catchInfo); // The way Maxine handles synchronized methods introduces an additional catch (Throwable) // which results in a negative bci value for the handler (since there is no user written // handler in the method). So we ignore this one and keep looking. if (isCaught && catchInfo.bci >= 0) { stackElementSizeAtCatch = stackElements.size() + 1; // this frame is added in the super call catchTargetMethod = tm; } } } return super.visitFrame(current, callee); } void walk(StackFrameWalker walker, Pointer ip, Pointer sp, Pointer fp, Throwable throwable) { walker.reset(); this.throwable = throwable; walkRaw(walker, ip, sp, fp); } /** * Resets state if the visitor is being reused. */ @Override void reset() { super.reset(); throwable = null; throwingMethodActor = null; catchTargetMethod = null; throwingBci = -1; stackElementSizeAtCatch = 0; } @NEVER_INLINE boolean uncaughtByApplication() { if (JVMTI.JVMTI_VM) { return false; } int catchCallerIndex = stackElements.size() - stackElementSizeAtCatch; for (int i = 0; i < catchCallerIndex; i++) { ClassMethodActor classMethodActor = stackElements.get(i).classMethodActor; if (!isVmStartup(classMethodActor)) { // application class lower than handler so its's caught return false; } } return true; } private boolean isVmStartup(ClassMethodActor classMethodActor) { ClassActor holder = classMethodActor.holder(); return holder.classLoader == VMClassLoader.VM_CLASS_LOADER || holder == JVMTIThreadFunctions.methodClassActor(); } boolean thrownInVmStartup() { if (JVMTI.JVMTI_VM) { return false; } for (int i = 0; i < stackElements.size(); i++) { ClassMethodActor classMethodActor = stackElements.get(i).classMethodActor; // check for non-VM class, but not the invocation stub for main which rethrows an unhandled exception if (!(isVmStartup(classMethodActor) || classMethodActor.holder().isReflectionStub())) { // genuine app class return false; } } // no app classes, in VM startup return true; } } private static boolean sendEvent() { if (JVMTIVmThreadLocal.bitIsSet(JVMTIVmThreadLocal.IN_UPCALL)) { // exception in JVMTI implementation, gets thrown to agent return false; } else { if (VmThread.current() == VmThread.vmOperationThread) { if (!JVMTI.JVMTI_VM) { // unless we are debugging the VM itself, this is also fatal. // we are likely in an upcall in an agent thread that submitted a VMOperation. return false; } } } // send if any agent wants it return JVMTI.eventNeeded(E.EXCEPTION) || JVMTI.eventNeeded(E.EXCEPTION_CATCH) || JVMTI.eventNeeded(E.METHOD_EXIT) || JVMTIThreadFunctions.framePopForException() || vmaHandler != null; } /** * Used by {@link EXCEPTION_CATCH} events to access the information need to dispatch the event. */ static ExceptionEventData getExceptionEventData() { return eventStateTL.get().exceptionEventData; } public static void raiseEvent(Throwable throwable, Pointer sp, Pointer fp, CodePointer ip) { EventState eventState = eventStateTL.get(); try { if (eventState.inProcess) { FatalError.unexpected("jvmti: exception while analyzing exception", throwable); } eventState.inProcess = true; if (!sendEvent()) { return; } ExceptionEventData exceptionEventData = eventState.exceptionEventData; exceptionEventData.throwable = throwable; StackAnalyzer stackAnalyser = eventState.stackAnalyser; stackAnalyser.reset(); stackAnalyser.walk(eventState.sfw, ip.toPointer(), sp, fp, throwable); // There is always a handler in Maxine but it may be unhandled by the application // and deciding this requires some detective work. It may also have been rethrown in the // VM startup sequence and we want to ignore that (since we previously decided it was uncaught) if (stackAnalyser.thrownInVmStartup()) { return; } TargetMethod ctm = stackAnalyser.catchTargetMethod; assert ctm != null; boolean uncaught = stackAnalyser.uncaughtByApplication(); if (uncaught) { exceptionEventData.catchMethodID = MethodID.fromWord(Word.zero()); exceptionEventData.catchLocation = 0; } else { exceptionEventData.catchMethodID = MethodID.fromMethodActor(ctm.classMethodActor); exceptionEventData.catchLocation = stackAnalyser.catchInfo.bci; } assert stackAnalyser.throwingMethodActor != null; exceptionEventData.location = stackAnalyser.throwingBci; exceptionEventData.methodID = MethodID.fromMethodActor(stackAnalyser.throwingMethodActor); if (JVMTI.eventNeeded(JVMTIEvents.E.EXCEPTION)) { JVMTI.event(JVMTIEvents.E.EXCEPTION, exceptionEventData); } if (!uncaught) { // send a POP_FRAME unless caught in throwing method if (ctm.classMethodActor != stackAnalyser.throwingMethodActor) { FramePopEventData framePopEventData = JVMTIThreadFunctions.getFramePopEventData(exceptionEventData.methodID, true, null); if (JVMTIThreadFunctions.findFramePopId(VmThread.current(), stackAnalyser.throwingFrameId)) { JVMTI.event(E.FRAME_POP, framePopEventData); } if (JVMTIEvents.isEventSet(E.METHOD_EXIT)) { JVMTI.event(E.METHOD_EXIT, framePopEventData); } if (vmaHandler != null) { vmaHandler.exceptionRaised(stackAnalyser.throwingMethodActor, throwable, stackAnalyser.throwingBci, stackAnalyser.stackElementSizeAtCatch - 1); } } // if an agent wants the exception catch event, we need to ensure that the compiled code for the catch method // has that capability, or deopt it and generate the relevant event code if (JVMTI.eventNeeded(JVMTIEvents.E.EXCEPTION_CATCH)) { JVMTICode.checkDeOptForMethod(ctm.classMethodActor, JVMTIEvents.codeEventSettings(null, VmThread.current())); } } } finally { eventState.inProcess = false; } } // VMA support public interface VMAHandler { void exceptionRaised(ClassMethodActor throwingActor, Throwable throwable, int bci, int poppedFrameCount); } private static VMAHandler vmaHandler; public static void registerVMAHAndler(VMAHandler handler) { vmaHandler = handler; } }