/* * 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.JVMTICallbacks.*; import static com.sun.max.vm.ext.jvmti.JVMTIConstants.*; import static com.sun.max.vm.ext.jvmti.JVMTIEnvNativeStruct.*; import static com.sun.max.vm.ext.jvmti.JVMTIEvents.*; import static com.sun.max.vm.ext.jvmti.JVMTIFieldWatch.*; import static com.sun.max.vm.intrinsics.MaxineIntrinsicIDs.*; import static com.sun.max.unsafe.UnsafeCast.*; import java.security.*; import java.util.*; import com.oracle.max.vm.ext.t1x.*; import com.oracle.max.vm.ext.t1x.jvmti.*; import com.sun.cri.bytecode.*; import com.sun.max.annotate.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.ext.jvmti.JVMTIUtil.ModeUnion; import com.sun.max.vm.ext.jvmti.JVMTIBreakpoints.*; import com.sun.max.vm.ext.jvmti.JVMTIException.*; import com.sun.max.vm.ext.jvmti.JVMTIThreadFunctions.*; import com.sun.max.vm.jni.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; import com.sun.max.vm.ti.*; /** * The heart of the Maxine JVMTI implementation. * Handles environments, event handling. * Supports both standard (native) agents and the Java agents written to {@link JJVMTI}. */ public class JVMTI { /** * Holds state associated with this side (i.e. Java) of the implementation that is independent * of whether this is a native or a Java agent. */ static abstract class Env { /** * Object tagging. */ JVMTITags tags = new JVMTITags(); /** * The global event settings for this agent. */ long globalEventSettings; /** * The per-thread event settings for this agent. */ JVMTIEvents.PerThreadSettings perThreadEventSettings = new JVMTIEvents.PerThreadSettings(); boolean isFree() { return false; } } /** * Subclass used for traditional native agents. */ static class NativeEnv extends Env { /** * The C struct used by the native agents. {@see JVMTIEnvNativeStruct}. */ Pointer cstruct; /** * Support for {@code Get/SetEnvironmentLocalStorage}. */ Pointer envStorage; /** * additions to the boot classpath by this agent. */ long[] bootClassPathAdd = new long[4]; @Override boolean isFree() { return cstruct.isZero(); } } /** * Abstract subclass used for agents that use {@link JJVMTI}. */ public static class JavaEnv extends Env { final EnumSet<JVMTICapabilities.E> capabilities = EnumSet.noneOf(JVMTICapabilities.E.class); final JJVMTI.EventCallbacks callbackHandler; protected JavaEnv(JJVMTI.EventCallbacks callbackHandler) { this.callbackHandler = callbackHandler; } } static class JVMTIHandler extends NullVMTIHandler implements VMTIHandler { @Override public void initialize() { JVMTI.initialize(); } @Override public void vmInitialized() { /* * JVMTI has a VM_START and a VM_INIT event, but the distinction * is minimal and only relates to which JVMTI functions may be called. * The gating function for dispatching either event this late * is that any JNI function can be called from VM_START, e.g, FindClass, * which requires that essentially all of the VM machinery is working. */ JVMTI.event(E.VM_START); JVMTI.event(E.VM_INIT); } @Override public void vmDeath() { JVMTI.event(E.VM_DEATH); } @Override public void threadStart(VmThread vmThread) { JVMTI.event(E.THREAD_START, vmThread); } @Override public void threadEnd(VmThread vmThread) { JVMTI.event(E.THREAD_END, vmThread); } @Override public boolean classFileLoadHookHandled() { return JVMTI.eventNeeded(E.CLASS_FILE_LOAD_HOOK); } @Override public byte[] classFileLoadHook(ClassLoader classLoader, String className, ProtectionDomain protectionDomain, byte[] classfileBytes) { return JVMTI.classFileLoadHook(classLoader, className, protectionDomain, classfileBytes); } @Override public void classLoad(ClassActor classActor) { // Have to send both events JVMTI.event(E.CLASS_LOAD, classActor); JVMTI.event(E.CLASS_PREPARE, classActor); } @Override public void methodCompiled(ClassMethodActor classMethodActor) { JVMTI.event(E.COMPILED_METHOD_LOAD, classMethodActor); } @Override public void methodUnloaded(ClassMethodActor classMethodActor, Pointer codeAddr) { MethodUnloadEventData methodUnloadEventData = threadMethodUnloadEventData.get(); methodUnloadEventData.classMethodActor = classMethodActor; methodUnloadEventData.codeAddr = codeAddr; JVMTI.event(E.COMPILED_METHOD_UNLOAD, methodUnloadEventData); } @Override public boolean hasBreakpoints(ClassMethodActor classMethodActor) { return JVMTIBreakpoints.hasBreakpoints(classMethodActor); } @Override public String bootclassPathExtension() { return JVMTIClassFunctions.getAddedBootClassPath(); } @Override public void beginUpcallVM() { JVMTIVmThreadLocal.setBit(VmThread.currentTLA(), JVMTIVmThreadLocal.IN_UPCALL); } @Override public void endUpcallVM() { JVMTIVmThreadLocal.unsetBit(VmThread.currentTLA(), JVMTIVmThreadLocal.IN_UPCALL); } @Override public void beginGC() { JVMTI.event(E.GARBAGE_COLLECTION_START); } @Override public void endGC() { JVMTI.event(E.GARBAGE_COLLECTION_FINISH); } @Override public boolean nativeCallNeedsPrologueAndEpilogue(MethodActor ma) { return ma != JVMTIFunctions.currentJniEnv; } @Override public void registerAgent(Word agentHandle) { JVMTI.setJVMTIEnv(agentHandle); } @Override public int activeAgents() { return JVMTI.activeEnvCount; } @Override public void raise(Throwable throwable, Pointer sp, Pointer fp, CodePointer ip) { JVMTIException.raiseEvent(throwable, sp, fp, ip); } @Override public RuntimeCompiler runtimeCompiler(RuntimeCompiler stdRuntimeCompiler) { return new T1X(JVMTI_T1XTemplateSource.class, new JVMTI_T1XCompilationFactory()); } @Override public boolean needsVMTICompilation(ClassMethodActor classMethodActor) { // N.B. We do not instrument reflection stubs. Amongst other reasons they // can be generated by upcalls from JVMTI agents using JNI, e.g. Class.getName, // causing runaway recursion. ClassActor holder = classMethodActor.holder(); return !holder.isReflectionStub() && JVMTI.compiledCodeEventsNeeded(classMethodActor); } @Override public boolean needsSpecialGetCallerClass() { return true; } @Override @NEVER_INLINE public Class getCallerClassForFindClass(int realFramesToSkip) { return JVMTIClassLoader.getCallerClassForFindClass(realFramesToSkip++); } } static { VMTI.registerEventHandler(new JVMTIHandler()); } private static final String AGENT_ONLOAD = "Agent_OnLoad"; private static final String AGENT_ONUNLOAD = "Agent_OnUnLoad"; /** * Since the agent initialization code happens very early, before we have a functional heap, * we static allocate storage for key data structures. The day that someone tries to runs * Maxine with more than {@link MAX_ENVS} agents, we will celebrate -;) */ static final int MAX_ENVS = 8; static final int MAX_NATIVE_ENVS = 6; /** * The record of registered agent environments, used to handle callbacks. * A free slot is denoted by {@link Env#cstruct} having a zero value. */ static final Env[] jvmtiEnvs; /** * The {@link #jvmtiEnvs} array is updated indirectly by the {@link #SetJVMTIEnv(Pointer)} upcall * during agent initialization. Hence we use a static variable to index the array during initialization. * N.B. after initialization this value should not be used to limit the search of the array as * agents may come and go. */ private static int nativeEnvsIndex; /** * The number of active agent environments. */ static int activeEnvCount; /** * The phase of execution. */ static int phase = JVMTI_PHASE_ONLOAD; static { jvmtiEnvs = new Env[MAX_ENVS]; for (int i = 0; i < MAX_NATIVE_ENVS; i++) { NativeEnv jvmtiEnv = new NativeEnv(); jvmtiEnvs[i] = jvmtiEnv; for (int j = 0; j < jvmtiEnv.bootClassPathAdd.length; j++) { jvmtiEnv.bootClassPathAdd[j] = 0; } } VMOptions.addFieldOption("-XX:", "JVMTI_VM", "Include VM classes in JVMTI results."); } /** * Called exactly once during agent startup (strictly speaking during jvmtiEnv creation in jvmti.c) * allowing it to be recorded once here to support the handling of callbacks to the agent. * @param agentHandle the jvmtienv C struct */ public static void setJVMTIEnv(Word agentHandle) { if (nativeEnvsIndex >= MAX_NATIVE_ENVS) { Log.println("too many JVMTI agents"); MaxineVM.native_exit(1); } NativeEnv nativeEnv = (NativeEnv) jvmtiEnvs[nativeEnvsIndex++]; nativeEnv.cstruct = agentHandle.asPointer(); activeEnvCount++; } /** * Called to register the environment for a Java JVMTI agent. * @param env */ public static synchronized void setJVMTIJavaEnv(Env env) { for (int i = MAX_NATIVE_ENVS; i < MAX_ENVS; i++) { if (jvmtiEnvs[i] == null) { jvmtiEnvs[i] = env; activeEnvCount++; return; } } Log.println("too many JVMTI agents"); MaxineVM.native_exit(1); } public static synchronized int disposeJVMTIJavaEnv(Env env) { for (int i = MAX_NATIVE_ENVS; i < MAX_ENVS; i++) { if (jvmtiEnvs[i] == env) { jvmtiEnvs[i] = null; activeEnvCount--; return JVMTI_ERROR_NONE; } } return JVMTI_ERROR_INVALID_ENVIRONMENT; } static Env getEnv(Pointer env) { for (int i = 0; i < MAX_NATIVE_ENVS; i++) { NativeEnv nativeEnv = (NativeEnv) jvmtiEnvs[i]; if (nativeEnv.cstruct == env) { return jvmtiEnvs[i]; } } return null; } static synchronized int disposeEnv(Pointer env) { for (int i = 0; i < MAX_NATIVE_ENVS; i++) { NativeEnv nativeEnv = (NativeEnv) jvmtiEnvs[i]; if (nativeEnv.cstruct == env) { // TODO cleanup nativeEnv.cstruct = Pointer.zero(); activeEnvCount--; return JVMTI_ERROR_NONE; } } return JVMTI_ERROR_INVALID_ENVIRONMENT; } /** * Initial entry from VM at the start of {@code PRISTINE} phase, i.e., before {code * VMConfiguration.initializeSchemes(MaxineVM.Phase.PRISTINE)} has been called. We call Agent_OnLoad for all the * agents listed in VM startup command. */ public static void initialize() { NativeInterfaces.initFunctionTable(getJVMTIInterface(-1), JVMTIFunctions.jvmtiFunctions, JVMTIFunctions.jvmtiFunctionActors); JVMTISystem.initSystemProperties(); for (int i = 0; i < AgentVMOption.count(); i++) { AgentVMOption.Info info = AgentVMOption.getInfo(i); Word handle = Word.zero(); Pointer path = info.libStart; if (info.isAbsolute) { handle = DynamicLinker.load(path); } else { handle = JVMTISystem.load(path); if (CString.equals(info.libStart, "jdwp")) { if (JVMTIVMOptions.jdwpLogOption.getValue()) { info.optionStart = CString.append(info.optionStart, ",logfile=/tmp/jdwp.log,logflags=255"); } } } if (handle.isZero()) { initializeFail("failed to load agentlib: ", info.libStart, ""); } Word onLoad = DynamicLinker.lookupSymbol(handle, AGENT_ONLOAD); if (onLoad.isZero()) { initializeFail("agentlib: ", info.libStart, " does not contain an Agent_OnLoad function"); } int rc = invokeAgentOnLoad(onLoad.asAddress(), info.optionStart); if (rc != 0) { initializeFail("agentlib: ", info.libStart, " failed to initialize"); } } // Invoke onBoot for any Java agents built into the image for (int i = MAX_NATIVE_ENVS; i < MAX_ENVS; i++) { JavaEnv javaEnv = (JavaEnv) jvmtiEnvs[i]; if (javaEnv != null) { javaEnv.callbackHandler.onBoot(); } } phase = JVMTI_PHASE_PRIMORDIAL; } static { new CriticalNativeMethod(JVMTI.class, "getJVMTIInterface"); } @C_FUNCTION private static native Pointer getJVMTIInterface(int version); private static void initializeFail(String s1, Pointer path, String s2) { Log.print(s1); Log.printCString(path); Log.println(s2); MaxineVM.native_exit(1); } /** * Support for avoiding unnecessary work in the VM. * Returns {@code true} iff at least one agent wants to handle this event. * @param eventId */ public static synchronized boolean eventNeeded(JVMTIEvents.E event) { if (MaxineVM.isHosted()) { return false; } if (activeEnvCount == 0) { return false; } if (phase == JVMTI_PHASE_DEAD) { return false; } for (int i = 0; i < jvmtiEnvs.length; i++) { if (hasCallbackForEvent(jvmtiEnvs[i], event, VmThread.current())) { return true; } } return false; } /** * Are there any agents requesting any events needing compiled code support? * @param classMethodActor the method about to be compiled or {@code null} if none. */ public static synchronized boolean compiledCodeEventsNeeded(ClassMethodActor classMethodActor) { return JVMTIEvents.anyCodeEventsSet() || (classMethodActor == null ? false : JVMTIBreakpoints.hasBreakpoints(classMethodActor)); } /** * Returns the {@link JVMTIEvents.E event} that corresponds to a given bytecode or null if none. * should it be instrumented. * @param opcode */ public static JVMTIEvents.E eventForBytecode(int opcode) { if (opcode == -1) { return E.METHOD_ENTRY; } else if (opcode == -2) { return E.METHOD_EXIT; } else { switch (opcode) { case Bytecodes.GETFIELD: case Bytecodes.GETSTATIC: return E.FIELD_ACCESS; case Bytecodes.PUTFIELD: case Bytecodes.PUTSTATIC: return E.FIELD_MODIFICATION; case Bytecodes.IRETURN: case Bytecodes.LRETURN: case Bytecodes.FRETURN: case Bytecodes.DRETURN: case Bytecodes.ARETURN: case Bytecodes.RETURN: return E.FRAME_POP; default: return null; } } } /** * Support for determining if we need to compile special code to dispatch * specific JVMTI events, e.g. METHOD_ENTRY, FIELD_ACCESS. * The value -1 is used to indicate METHOD_ENTRY as this is a pseudo bytecode. * @return the {@link JVMTIEvents.E event} corresponding to the bytecode or null if no associated * event or not set for an agent */ public static synchronized E byteCodeEventNeeded(int opcode) { E event = eventForBytecode(opcode); if (event != null) { if (!JVMTIEvents.isEventSet(event)) { event = null; } } return event; } /** * Gets the (enabled) callback for given event in given environment. * @param jvmtiEnv * @param eventId * @param vmThread thread generating the event * @return the callback address or zero if none or not enabled */ static Pointer getCallbackForEvent(NativeEnv jvmtiEnv, JVMTIEvents.E event, VmThread vmThread) { if (jvmtiEnv.isFree()) { return Pointer.zero(); } if (JVMTIEvents.isEventSet(jvmtiEnv, event, vmThread)) { return getCallBack(CALLBACKS.getPtr(jvmtiEnv.cstruct), event); } return Pointer.zero(); } static boolean hasCallbackForEvent(Env jvmtiEnv, JVMTIEvents.E event, VmThread vmThread) { if (jvmtiEnv == null || jvmtiEnv.isFree()) { return false; } return JVMTIEvents.isEventSet(jvmtiEnv, event, vmThread); } /** * Invoked from T1X templates, so no inline. */ @NEVER_INLINE public static void methodEntryEvent(MethodActor methodActor) { event(E.METHOD_ENTRY, methodActor); } /** * Invoked from T1X templates, so no inline. */ @NEVER_INLINE public static void exceptionCatchEvent(Object exception) { event(E.EXCEPTION_CATCH, JVMTIException.getExceptionEventData()); } private static void logEvent(JVMTIEvents.E event, JVMTI.Env env, int status, Object arg1) { if (JVMTIEvents.logger.enabled()) { JVMTIEvents.logger.logEvent(event, status, env, arg1); } } public static void event(JVMTIEvents.E event) { event(event, null); } /** * Dispatches the event denoted by {@code eventId} to all environments that have registered and enabled a call back * for it. * * @param eventId */ public static void event(JVMTIEvents.E event, Object arg1) { if (MaxineVM.isHosted()) { return; } if (phase == JVMTI_PHASE_LIVE && activeEnvCount == 0) { logEvent(event, null, JVMTIEventLogger.NO_INTEREST, arg1); return; } // Regardless of interest in these events we must track the phase. switch (event) { case VM_START: phase = JVMTI_PHASE_START; break; case VM_INIT: phase = JVMTI_PHASE_LIVE; break; default: } if ((JVMTIEvents.getPhases(event) & phase) == 0) { // VM has sent an event that is not supposed to be delivered in the current phase logEvent(event, null, JVMTIEventLogger.WRONG_PHASE, arg1); } else { // fast check if anyone is interested boolean interest = JVMTIEvents.isEventSet(event); if (interest) { interest = dispatchEvent(event, arg1); // did anyone actually get it? } if (!interest) { logEvent(event, null, JVMTIEventLogger.NO_INTEREST, arg1); } } if (event == E.VM_DEATH) { // Now the event has (possibly) been delivered change the phase. // This will suppress the deliver of any future events as the phase check above will not match. phase = JVMTI_PHASE_DEAD; } } private static boolean dispatchEvent(JVMTIEvents.E event, Object arg1) { // Dispatch event to all interested agents boolean interest = false; for (int i = 0; i < jvmtiEnvs.length; i++) { if (i < MAX_NATIVE_ENVS) { NativeEnv nativeEnv = (NativeEnv) jvmtiEnvs[i]; Pointer callback = getCallbackForEvent(nativeEnv, event, VmThread.current()); if (callback.isZero()) { continue; } interest = true; logEvent(event, nativeEnv, JVMTIEventLogger.DELIVERED, arg1); Pointer cstruct = nativeEnv.cstruct; switch (event) { case VM_START: case VM_DEATH: invokeStartFunctionNoArg(callback, cstruct); break; case VM_INIT: case THREAD_START: case THREAD_END: invokeStartFunction(callback, cstruct, currentThreadHandle()); break; case GARBAGE_COLLECTION_START: case GARBAGE_COLLECTION_FINISH: invokeGarbageCollectionCallback(callback, cstruct); break; case METHOD_ENTRY: invokeThreadObjectCallback(callback, cstruct, currentThreadHandle(), MethodID.fromMethodActor(asClassMethodActor(arg1))); break; case CLASS_LOAD: case CLASS_PREPARE: invokeThreadObjectCallback(callback, cstruct, currentThreadHandle(), JniHandles.createLocalHandle(asClassActor(arg1).javaClass())); break; case COMPILED_METHOD_LOAD: { ClassMethodActor cma = asClassMethodActor(arg1); TargetMethod tm = cma.currentTargetMethod(); invokeCompiledMethodLoadCallback(callback, cstruct, MethodID.fromMethodActor(cma), tm.codeLength(), tm.start(), 0, Pointer.zero(), Pointer.zero()); break; } case COMPILED_METHOD_UNLOAD: { MethodUnloadEventData methodUnloadEventData = asMethodUnloadEventData(arg1); invokeCompiledMethodUnloadCallback(callback, cstruct, MethodID.fromMethodActor(methodUnloadEventData.classMethodActor), methodUnloadEventData.codeAddr.asAddress()); break; } case FIELD_ACCESS: case FIELD_MODIFICATION: invokeFieldAccessCallback(callback, cstruct, currentThreadHandle(), asFieldEventData(arg1)); break; case BREAKPOINT: case SINGLE_STEP: EventBreakpointID id = asEventBreakpointID(arg1); invokeBreakpointCallback(callback, cstruct, currentThreadHandle(), id.methodID, id.location); break; case FRAME_POP: FramePopEventData framePopEventData = asFramePopEventData(arg1); invokeFramePopCallback(callback, cstruct, currentThreadHandle(), framePopEventData.methodID, framePopEventData.wasPoppedByException); break; case EXCEPTION: case EXCEPTION_CATCH: ExceptionEventData exceptionEventData = asExceptionEventData(arg1); invokeExceptionCallback(callback, cstruct, event == E.EXCEPTION_CATCH, currentThreadHandle(), exceptionEventData.methodID, exceptionEventData.location, JniHandles.createLocalHandle(exceptionEventData.throwable), exceptionEventData.catchMethodID, exceptionEventData.catchLocation); break; } } else { JavaEnv javaEnv = (JavaEnv) jvmtiEnvs[i]; if (javaEnv == null || !JVMTIEvents.isEventSet(javaEnv, event, VmThread.current())) { continue; } interest = true; logEvent(event, javaEnv, JVMTIEventLogger.DELIVERED, arg1); Thread currentThread = Thread.currentThread(); switch (event) { case VM_INIT: javaEnv.callbackHandler.vmInit(); break; case VM_DEATH: javaEnv.callbackHandler.vmDeath(); break; case THREAD_START: javaEnv.callbackHandler.threadStart(currentThread); break; case THREAD_END: javaEnv.callbackHandler.threadEnd(currentThread); break; case GARBAGE_COLLECTION_START: javaEnv.callbackHandler.garbageCollectionStart(); break; case GARBAGE_COLLECTION_FINISH: javaEnv.callbackHandler.garbageCollectionFinish(); break; case CLASS_LOAD: javaEnv.callbackHandler.classLoad(currentThread, asClassActor(arg1)); break; case COMPILED_METHOD_LOAD: { ClassMethodActor cma = asClassMethodActor(arg1); TargetMethod tm = cma.currentTargetMethod(); javaEnv.callbackHandler.compiledMethodLoad(cma, tm.codeLength(), tm.start(), null, null); break; } case COMPILED_METHOD_UNLOAD: { MethodUnloadEventData methodUnloadEventData = asMethodUnloadEventData(arg1); javaEnv.callbackHandler.compiledMethodUnload(methodUnloadEventData.classMethodActor, methodUnloadEventData.codeAddr); break; } case METHOD_ENTRY: { javaEnv.callbackHandler.methodEntry(currentThread, asClassMethodActor(arg1)); break; } case METHOD_EXIT: { FramePopEventData framePopEventData = asFramePopEventData(arg1); javaEnv.callbackHandler.methodExit(currentThread, MethodID.toMethodActor(framePopEventData.methodID), framePopEventData.wasPoppedByException, framePopEventData.value); break; } case FIELD_ACCESS: case FIELD_MODIFICATION: { invokeFieldAccessCallback(javaEnv.callbackHandler, currentThread, asFieldEventData(arg1)); break; } case SINGLE_STEP: case BREAKPOINT: EventBreakpointID id = asEventBreakpointID(arg1); MethodID methodId = MethodID.fromWord(Address.fromLong(id.methodID)); javaEnv.callbackHandler.breakpoint(currentThread, MethodID.toMethodActor(methodId), id.location); break; case FRAME_POP: FramePopEventData framePopEventData = asFramePopEventData(arg1); javaEnv.callbackHandler.framePop(currentThread, MethodID.toMethodActor(framePopEventData.methodID), framePopEventData.wasPoppedByException); break; case EXCEPTION: { ExceptionEventData exceptionEventData = asExceptionEventData(arg1); MethodActor catchMethod = exceptionEventData.catchMethodID.isZero() ? null : MethodID.toMethodActor(exceptionEventData.catchMethodID); javaEnv.callbackHandler.exception(currentThread, MethodID.toMethodActor(exceptionEventData.methodID), exceptionEventData.location, exceptionEventData.throwable, catchMethod, exceptionEventData.catchLocation); break; } case EXCEPTION_CATCH: { ExceptionEventData exceptionEventData = asExceptionEventData(arg1); javaEnv.callbackHandler.exceptionCatch(currentThread, MethodID.toMethodActor(exceptionEventData.methodID), exceptionEventData.location, exceptionEventData.throwable); break; } default: assert false; } } } return interest; // at least one agent was (really) interested. } private static class ThreadFieldEventData extends ThreadLocal<FieldEventData> { @Override public FieldEventData initialValue() { return new FieldEventData(); } } private static class MethodUnloadEventData { ClassMethodActor classMethodActor; Pointer codeAddr; } private static class ThreadMethodUnloadEventData extends ThreadLocal<MethodUnloadEventData> { @Override public MethodUnloadEventData initialValue() { return new MethodUnloadEventData(); } } private static final ThreadFieldEventData threadFieldEventData = new ThreadFieldEventData(); private static final ThreadMethodUnloadEventData threadMethodUnloadEventData = new ThreadMethodUnloadEventData(); @INTRINSIC(UNSAFE_CAST) public static FieldEventData asFieldEventData(Object object) { return (FieldEventData) object; } @INTRINSIC(UNSAFE_CAST) public static MethodUnloadEventData asMethodUnloadEventData(Object object) { return (MethodUnloadEventData) object; } @INTRINSIC(UNSAFE_CAST) public static FramePopEventData asFramePopEventData(Object object) { return (FramePopEventData) object; } @INTRINSIC(UNSAFE_CAST) public static EventBreakpointID asEventBreakpointID(Object object) { return (EventBreakpointID) object; } @INTRINSIC(UNSAFE_CAST) public static ExceptionEventData asExceptionEventData(Object object) { return (ExceptionEventData) object; } private static FieldEventData setFieldEventData(JVMTIEvents.E event, Object object, int offset, boolean isStatic) { FieldEventData data = threadFieldEventData.get(); data.object = object; data.offset = offset; data.isStatic = isStatic; return data; } // These event methods are used in T1X templates, so must not be inlined. @NEVER_INLINE public static void fieldAccessEvent(Object object, int offset, boolean isStatic) { FieldEventData data = setFieldEventData(E.FIELD_ACCESS, object, offset, isStatic); data.tag = FieldEventData.DATA_NONE; event(E.FIELD_ACCESS, data); } @NEVER_INLINE public static void fieldModificationEvent(Object object, int offset, boolean isStatic, long value) { FieldEventData data = setFieldEventData(E.FIELD_MODIFICATION, object, offset, isStatic); data.tag = FieldEventData.DATA_LONG; data.longValue = value; event(E.FIELD_MODIFICATION, data); } @NEVER_INLINE public static void fieldModificationEvent(Object object, int offset, boolean isStatic, float value) { FieldEventData data = setFieldEventData(E.FIELD_MODIFICATION, object, offset, isStatic); data.tag = FieldEventData.DATA_FLOAT; data.floatValue = value; event(E.FIELD_MODIFICATION, data); } @NEVER_INLINE public static void fieldModificationEvent(Object object, int offset, boolean isStatic, double value) { FieldEventData data = setFieldEventData(E.FIELD_MODIFICATION, object, offset, isStatic); data.tag = FieldEventData.DATA_DOUBLE; data.doubleValue = value; event(E.FIELD_MODIFICATION, data); } @NEVER_INLINE public static void fieldModificationEvent(Object object, int offset, boolean isStatic, Object value) { FieldEventData data = setFieldEventData(E.FIELD_MODIFICATION, object, offset, isStatic); data.tag = FieldEventData.DATA_OBJECT; data.objectValue = value; event(E.FIELD_MODIFICATION, data); } public static byte[] classFileLoadHook(ClassLoader classLoader, String className, ProtectionDomain protectionDomain, byte[] classfileBytes) { return JVMTIClassFunctions.classFileLoadHook(classLoader, className, protectionDomain, classfileBytes); } public static String getAddedBootClassPath() { return JVMTIClassFunctions.getAddedBootClassPath(); } private static JniHandle currentThreadHandle() { return JniHandles.createLocalHandle(VmThread.current().javaThread()); } private static Pointer getCallBack(Pointer callbacks, JVMTIEvents.E event) { int index = event.ordinal(); return callbacks.readWord(index * Pointer.size()).asPointer(); } private static final String JAVA_HOME = "java.home"; private static final byte[] JAVA_HOME_BYTES = "JAVA_HOME".getBytes(); static int getPhase(Pointer phasePtr) { int result = getPhase(); phasePtr.setInt(0, result); return JVMTI_ERROR_NONE; } static int getPhase() { return phase == JVMTI_PHASE_START ? JVMTI_PHASE_START_ORIG : phase; } private static class AgentThreadUnion extends ModeUnion { final Thread thread; final int priority; // native Pointer env; Address proc; Pointer arg; AgentThreadUnion(boolean isNative, Thread thread, int priority) { super(isNative); this.thread = thread; this.priority = priority; } } static void runAgentThread(Thread thread, int priority) { AgentThreadUnion agu = new AgentThreadUnion(false, thread, priority); runAgentThread(agu); } static int runAgentThread(Pointer env, JniHandle jthread, Address proc, Pointer arg, int priority) { AgentThreadUnion agu = new AgentThreadUnion(true, (Thread) jthread.unhand(), priority); agu.env = env; agu.proc = proc; agu.arg = arg; return runAgentThread(agu); } static int runAgentThread(AgentThreadUnion agu) { /* * The JVMTI spec for this says: * * "The thread group of the thread is ignored -- specifically, the thread is not added to the thread group * and the thread is not seen on queries of the thread group at either the Java programming language or JVM TI levels. * The thread is not visible to Java programming language queries but is included in JVM TI queries * (for example, GetAllThreads and GetAllStackTraces)." * * For the native variant, there is no runnable method associated with the thread at this stage, and the default * run method just returns. So, knowing the implementation of Thread, we create a runnable and * patch the "Runnable target" with an object that will invoke the "proc" native method. */ agu.thread.setPriority(agu.priority); agu.thread.setDaemon(true); if (agu.isNative) { new AgentThreadRunnable(agu.env, agu.thread, agu.proc.asPointer(), agu.arg); } // calling VmThread.start0 instead of Thread.start avoids adding the thread // to whatever thread group was set when the constructor was invoked. final VmThread vmThread = VmThreadFactory.create(agu.thread); vmThread.setAsJVMTIAgentThread(); vmThread.start0(); return JVMTI_ERROR_NONE; } /** * Provides the {@link Runnable} to plug into the the agent thread's "target" field so that when * it is started it will invoke the {@code run} method defined here, which invokes the native * agent thread method. */ static class AgentThreadRunnable implements Runnable { // pseudo field to access the Thread.target field @ALIAS(declaringClass = Thread.class) Runnable target; @INTRINSIC(UNSAFE_CAST) static native AgentThreadRunnable asAgentThreadRunnable(Object object); Pointer jvmtiEnv; Pointer nativeProc; Pointer arg; AgentThreadRunnable(Pointer env, Thread agentThread, Pointer proc, Pointer arg) { jvmtiEnv = env; nativeProc = proc; this.arg = arg; AgentThreadRunnable proxy = asAgentThreadRunnable(agentThread); proxy.target = this; } public void run() { invokeStartFunction(nativeProc, jvmtiEnv, arg); } } /** * In it's default setting, VM classes are invisible to JVMTI. * They don't show up in stack traces, heap traces or the set of loaded classes. */ static boolean JVMTI_VM; }