/* * Copyright 2003-2008 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ /* * Copyright 2003 Wily Technology, Inc. */ #include #include #include #include #include "JPLISAgent.h" #include "JPLISAssert.h" #include "Utilities.h" #include "Reentrancy.h" #include "JavaExceptions.h" #include "EncodingSupport.h" #include "FileSystemSupport.h" /* For MAXPATHLEN & uintptr_t */ #include "sun_instrument_InstrumentationImpl.h" /* * The JPLISAgent manages the initialization all of the Java programming language Agents. * It also supports the native method bridge between the JPLIS and the JVMTI. * It maintains a single JVMTI Env that all JPL agents share. * It parses command line requests and creates individual Java agents. */ /* * private prototypes */ /* Allocates an unformatted JPLIS agent data structure. Returns NULL if allocation fails. */ JPLISAgent * allocateJPLISAgent(jvmtiEnv * jvmtiEnv); /* Initializes an already-allocated JPLIS agent data structure. */ JPLISInitializationError initializeJPLISAgent( JPLISAgent * agent, JavaVM * vm, jvmtiEnv * jvmtienv); /* De-allocates a JPLIS agent data structure. Only used in partial-failure cases at startup; * in normal usage the JPLIS agent lives forever */ void deallocateJPLISAgent( jvmtiEnv * jvmtienv, JPLISAgent * agent); /* Does one-time work to interrogate the JVM about capabilities and cache the answers. */ void checkCapabilities(JPLISAgent * agent); /* Takes the elements of the command string (agent class name and options string) and * create java strings for them. * Returns true if a classname was found. Makes no promises beyond the textual; says nothing about whether * the class exists or can be loaded. * If return value is true, sets outputClassname to a non-NULL local JNI reference. * If return value is true, sets outputOptionsString either to NULL or to a non-NULL local JNI reference. * If return value is false, neither output parameter is set. */ jboolean commandStringIntoJavaStrings( JNIEnv * jnienv, const char * classname, const char * optionsString, jstring * outputClassname, jstring * outputOptionsString); /* Start one Java agent from the supplied parameters. * Most of the logic lives in a helper function that lives over in Java code-- * we pass parameters out to Java and use our own Java helper to actually * load the agent and call the premain. * Returns true if the Java agent class is loaded and the premain/agentmain method completes * with no exceptions, false otherwise. */ jboolean invokeJavaAgentMainMethod( JNIEnv * jnienv, jobject instrumentationImpl, jmethodID agentMainMethod, jstring className, jstring optionsString); /* Once we have loaded the Java agent and called the premain, * we can release the copies we have been keeping of the command line * data (agent class name and option strings). */ void deallocateCommandLineData(JPLISAgent * agent); /* * Common support for various class list fetchers. */ typedef jvmtiError (*ClassListFetcher) ( jvmtiEnv * jvmtiEnv, jobject classLoader, jint * classCount, jclass ** classes); /* Fetcher that ignores the class loader parameter, and uses the JVMTI to get a list of all classes. * Returns a jvmtiError according to the underlying JVMTI service. */ jvmtiError getAllLoadedClassesClassListFetcher( jvmtiEnv * jvmtiEnv, jobject classLoader, jint * classCount, jclass ** classes); /* Fetcher that uses the class loader parameter, and uses the JVMTI to get a list of all classes * for which the supplied loader is the initiating loader. * Returns a jvmtiError according to the underlying JVMTI service. */ jvmtiError getInitiatedClassesClassListFetcher( jvmtiEnv * jvmtiEnv, jobject classLoader, jint * classCount, jclass ** classes); /* * Common guts for two native methods, which are the same except for the policy for fetching * the list of classes. * Either returns a local JNI reference to an array of references to java.lang.Class. * Can throw, if it does will alter the JNIEnv with an outstanding exception. */ jobjectArray commonGetClassList( JNIEnv * jnienv, JPLISAgent * agent, jobject classLoader, ClassListFetcher fetcher); /* * Misc. utilities. */ /* Checked exception mapper used by the redefine classes implementation. * Allows ClassNotFoundException or UnmodifiableClassException; maps others * to InternalError. Can return NULL in an error case. */ jthrowable redefineClassMapper( JNIEnv * jnienv, jthrowable throwableToMap); /* Turns a buffer of jclass * into a Java array whose elements are java.lang.Class. * Can throw, if it does will alter the JNIEnv with an outstanding exception. */ jobjectArray getObjectArrayFromClasses(JNIEnv* jnienv, jclass* classes, jint classCount); JPLISEnvironment * getJPLISEnvironment(jvmtiEnv * jvmtienv) { JPLISEnvironment * environment = NULL; jvmtiError jvmtierror = JVMTI_ERROR_NONE; jvmtierror = (*jvmtienv)->GetEnvironmentLocalStorage( jvmtienv, (void**)&environment); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if (jvmtierror == JVMTI_ERROR_NONE) { jplis_assert(environment != NULL); jplis_assert(environment->mJVMTIEnv == jvmtienv); } else { environment = NULL; } return environment; } /* * OnLoad processing code. */ /* * Creates a new JPLISAgent. * Returns error if the agent cannot be created and initialized. * The JPLISAgent* pointed to by agent_ptr is set to the new broker, * or NULL if an error has occurred. */ JPLISInitializationError createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; jvmtiEnv * jvmtienv = NULL; jint jnierror = JNI_OK; *agent_ptr = NULL; jnierror = (*vm)->GetEnv( vm, (void **) &jvmtienv, JVMTI_VERSION); if ( jnierror != JNI_OK ) { initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT; } else { JPLISAgent * agent = allocateJPLISAgent(jvmtienv); if ( agent == NULL ) { initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE; } else { initerror = initializeJPLISAgent( agent, vm, jvmtienv); if ( initerror == JPLIS_INIT_ERROR_NONE ) { *agent_ptr = agent; } else { deallocateJPLISAgent(jvmtienv, agent); } } /* don't leak envs */ if ( initerror != JPLIS_INIT_ERROR_NONE ) { jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment(jvmtienv); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } } return initerror; } /* * Allocates a JPLISAgent. Returns NULL if it cannot be allocated */ JPLISAgent * allocateJPLISAgent(jvmtiEnv * jvmtienv) { return (JPLISAgent *) allocate( jvmtienv, sizeof(JPLISAgent)); } JPLISInitializationError initializeJPLISAgent( JPLISAgent * agent, JavaVM * vm, jvmtiEnv * jvmtienv) { jvmtiError jvmtierror = JVMTI_ERROR_NONE; jvmtiPhase phase; agent->mJVM = vm; agent->mNormalEnvironment.mJVMTIEnv = jvmtienv; agent->mNormalEnvironment.mAgent = agent; agent->mNormalEnvironment.mIsRetransformer = JNI_FALSE; agent->mRetransformEnvironment.mJVMTIEnv = NULL; /* NULL until needed */ agent->mRetransformEnvironment.mAgent = agent; agent->mRetransformEnvironment.mIsRetransformer = JNI_FALSE; /* JNI_FALSE until mJVMTIEnv is set */ agent->mAgentmainCaller = NULL; agent->mInstrumentationImpl = NULL; agent->mPremainCaller = NULL; agent->mTransform = NULL; agent->mRedefineAvailable = JNI_FALSE; /* assume no for now */ agent->mRedefineAdded = JNI_FALSE; agent->mNativeMethodPrefixAvailable = JNI_FALSE; /* assume no for now */ agent->mNativeMethodPrefixAdded = JNI_FALSE; agent->mAgentClassName = NULL; agent->mOptionsString = NULL; /* make sure we can recover either handle in either direction. * the agent has a ref to the jvmti; make it mutual */ jvmtierror = (*jvmtienv)->SetEnvironmentLocalStorage( jvmtienv, &(agent->mNormalEnvironment)); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); /* check what capabilities are available */ checkCapabilities(agent); /* check phase - if live phase then we don't need the VMInit event */ jvmtierror = (*jvmtienv)->GetPhase(jvmtienv, &phase); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if (phase == JVMTI_PHASE_LIVE) { return JPLIS_INIT_ERROR_NONE; } if (phase != JVMTI_PHASE_ONLOAD) { /* called too early or called too late; either way bail out */ return JPLIS_INIT_ERROR_FAILURE; } /* now turn on the VMInit event */ if ( jvmtierror == JVMTI_ERROR_NONE ) { jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.VMInit = &eventHandlerVMInit; jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv, &callbacks, sizeof(callbacks)); check_phase_ret_blob(jvmtierror, JPLIS_INIT_ERROR_FAILURE); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } if ( jvmtierror == JVMTI_ERROR_NONE ) { jvmtierror = (*jvmtienv)->SetEventNotificationMode( jvmtienv, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL /* all threads */); check_phase_ret_blob(jvmtierror, JPLIS_INIT_ERROR_FAILURE); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } return (jvmtierror == JVMTI_ERROR_NONE)? JPLIS_INIT_ERROR_NONE : JPLIS_INIT_ERROR_FAILURE; } void deallocateJPLISAgent(jvmtiEnv * jvmtienv, JPLISAgent * agent) { deallocate(jvmtienv, agent); } JPLISInitializationError recordCommandLineData( JPLISAgent * agent, const char * agentClassName, const char * optionsString ) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; char * ourCopyOfAgentClassName = NULL; char * ourCopyOfOptionsString = NULL; /* if no actual params, bail out now */ if ((agentClassName == NULL) || (*agentClassName == 0)) { initerror = JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED; } else { ourCopyOfAgentClassName = allocate(jvmti(agent), strlen(agentClassName)+1); if (ourCopyOfAgentClassName == NULL) { initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE; } else { if (optionsString != NULL) { ourCopyOfOptionsString = allocate(jvmti(agent), strlen(optionsString)+1); if (ourCopyOfOptionsString == NULL) { deallocate(jvmti(agent), ourCopyOfAgentClassName); initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE; } } } } if (initerror == JPLIS_INIT_ERROR_NONE) { strcpy(ourCopyOfAgentClassName, agentClassName); if (optionsString != NULL) { strcpy(ourCopyOfOptionsString, optionsString); } agent->mAgentClassName = ourCopyOfAgentClassName; agent->mOptionsString = ourCopyOfOptionsString; } return initerror; } /* * VMInit processing code. */ /* * If this call fails, the JVM launch will ultimately be aborted, * so we don't have to be super-careful to clean up in partial failure * cases. */ jboolean processJavaStart( JPLISAgent * agent, JNIEnv * jnienv) { jboolean result; /* * OK, Java is up now. We can start everything that needs Java. */ /* * First make our emergency fallback InternalError throwable. */ result = initializeFallbackError(jnienv); jplis_assert(result); /* * Now make the InstrumentationImpl instance. */ if ( result ) { result = createInstrumentationImpl(jnienv, agent); jplis_assert(result); } /* * Then turn off the VMInit handler and turn on the ClassFileLoadHook. * This way it is on before anyone registers a transformer. */ if ( result ) { result = setLivePhaseEventHandlers(agent); jplis_assert(result); } /* * Load the Java agent, and call the premain. */ if ( result ) { result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString, agent->mPremainCaller); } /* * Finally surrender all of the tracking data that we don't need any more. * If something is wrong, skip it, we will be aborting the JVM anyway. */ if ( result ) { deallocateCommandLineData(agent); } return result; } jboolean startJavaAgent( JPLISAgent * agent, JNIEnv * jnienv, const char * classname, const char * optionsString, jmethodID agentMainMethod) { jboolean success = JNI_FALSE; jstring classNameObject = NULL; jstring optionsStringObject = NULL; success = commandStringIntoJavaStrings( jnienv, classname, optionsString, &classNameObject, &optionsStringObject); if (success) { success = invokeJavaAgentMainMethod( jnienv, agent->mInstrumentationImpl, agentMainMethod, classNameObject, optionsStringObject); } return success; } void deallocateCommandLineData( JPLISAgent * agent) { deallocate(jvmti(agent), (void*)agent->mAgentClassName); deallocate(jvmti(agent), (void*)agent->mOptionsString); /* zero things out so it is easier to see what is going on */ agent->mAgentClassName = NULL; agent->mOptionsString = NULL; } /* * Create the java.lang.instrument.Instrumentation instance * and access information for it (method IDs, etc) */ jboolean createInstrumentationImpl( JNIEnv * jnienv, JPLISAgent * agent) { jclass implClass = NULL; jboolean errorOutstanding = JNI_FALSE; jobject resultImpl = NULL; jmethodID premainCallerMethodID = NULL; jmethodID agentmainCallerMethodID = NULL; jmethodID transformMethodID = NULL; jmethodID constructorID = NULL; jobject localReference = NULL; /* First find the class of our implementation */ implClass = (*jnienv)->FindClass( jnienv, JPLIS_INSTRUMENTIMPL_CLASSNAME); errorOutstanding = checkForAndClearThrowable(jnienv); errorOutstanding = errorOutstanding || (implClass == NULL); jplis_assert_msg(!errorOutstanding, "find class on InstrumentationImpl failed"); if ( !errorOutstanding ) { constructorID = (*jnienv)->GetMethodID( jnienv, implClass, JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODNAME, JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODSIGNATURE); errorOutstanding = checkForAndClearThrowable(jnienv); errorOutstanding = errorOutstanding || (constructorID == NULL); jplis_assert_msg(!errorOutstanding, "find constructor on InstrumentationImpl failed"); } if ( !errorOutstanding ) { jlong peerReferenceAsScalar = (jlong)(intptr_t) agent; localReference = (*jnienv)->NewObject( jnienv, implClass, constructorID, peerReferenceAsScalar, agent->mRedefineAdded, agent->mNativeMethodPrefixAdded); errorOutstanding = checkForAndClearThrowable(jnienv); errorOutstanding = errorOutstanding || (localReference == NULL); jplis_assert_msg(!errorOutstanding, "call constructor on InstrumentationImpl failed"); } if ( !errorOutstanding ) { resultImpl = (*jnienv)->NewGlobalRef(jnienv, localReference); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "copy local ref to global ref"); } /* Now look up the method ID for the pre-main caller (we will need this more than once) */ if ( !errorOutstanding ) { premainCallerMethodID = (*jnienv)->GetMethodID( jnienv, implClass, JPLIS_INSTRUMENTIMPL_PREMAININVOKER_METHODNAME, JPLIS_INSTRUMENTIMPL_PREMAININVOKER_METHODSIGNATURE); errorOutstanding = checkForAndClearThrowable(jnienv); errorOutstanding = errorOutstanding || (premainCallerMethodID == NULL); jplis_assert_msg(!errorOutstanding, "can't find premain invoker methodID"); } /* Now look up the method ID for the agent-main caller */ if ( !errorOutstanding ) { agentmainCallerMethodID = (*jnienv)->GetMethodID( jnienv, implClass, JPLIS_INSTRUMENTIMPL_AGENTMAININVOKER_METHODNAME, JPLIS_INSTRUMENTIMPL_AGENTMAININVOKER_METHODSIGNATURE); errorOutstanding = checkForAndClearThrowable(jnienv); errorOutstanding = errorOutstanding || (agentmainCallerMethodID == NULL); jplis_assert_msg(!errorOutstanding, "can't find agentmain invoker methodID"); } /* Now look up the method ID for the transform method (we will need this constantly) */ if ( !errorOutstanding ) { transformMethodID = (*jnienv)->GetMethodID( jnienv, implClass, JPLIS_INSTRUMENTIMPL_TRANSFORM_METHODNAME, JPLIS_INSTRUMENTIMPL_TRANSFORM_METHODSIGNATURE); errorOutstanding = checkForAndClearThrowable(jnienv); errorOutstanding = errorOutstanding || (transformMethodID == NULL); jplis_assert_msg(!errorOutstanding, "can't find transform methodID"); } if ( !errorOutstanding ) { agent->mInstrumentationImpl = resultImpl; agent->mPremainCaller = premainCallerMethodID; agent->mAgentmainCaller = agentmainCallerMethodID; agent->mTransform = transformMethodID; } return !errorOutstanding; } jboolean commandStringIntoJavaStrings( JNIEnv * jnienv, const char * classname, const char * optionsString, jstring * outputClassname, jstring * outputOptionsString) { jstring classnameJavaString = NULL; jstring optionsJavaString = NULL; jboolean errorOutstanding = JNI_TRUE; classnameJavaString = (*jnienv)->NewStringUTF(jnienv, classname); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't create class name java string"); if ( !errorOutstanding ) { if ( optionsString != NULL) { optionsJavaString = (*jnienv)->NewStringUTF(jnienv, optionsString); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't create options java string"); } if ( !errorOutstanding ) { *outputClassname = classnameJavaString; *outputOptionsString = optionsJavaString; } } return !errorOutstanding; } jboolean invokeJavaAgentMainMethod( JNIEnv * jnienv, jobject instrumentationImpl, jmethodID mainCallingMethod, jstring className, jstring optionsString) { jboolean errorOutstanding = JNI_FALSE; jplis_assert(mainCallingMethod != NULL); if ( mainCallingMethod != NULL ) { (*jnienv)->CallVoidMethod( jnienv, instrumentationImpl, mainCallingMethod, className, optionsString); errorOutstanding = checkForThrowable(jnienv); if ( errorOutstanding ) { logThrowable(jnienv); } checkForAndClearThrowable(jnienv); } return !errorOutstanding; } jboolean setLivePhaseEventHandlers( JPLISAgent * agent) { jvmtiEventCallbacks callbacks; jvmtiEnv * jvmtienv = jvmti(agent); jvmtiError jvmtierror; /* first swap out the handlers (switch from the VMInit handler, which we do not need, * to the ClassFileLoadHook handler, which is what the agents need from now on) */ memset(&callbacks, 0, sizeof(callbacks)); callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv, &callbacks, sizeof(callbacks)); check_phase_ret_false(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if ( jvmtierror == JVMTI_ERROR_NONE ) { /* turn off VMInit */ jvmtierror = (*jvmtienv)->SetEventNotificationMode( jvmtienv, JVMTI_DISABLE, JVMTI_EVENT_VM_INIT, NULL /* all threads */); check_phase_ret_false(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } if ( jvmtierror == JVMTI_ERROR_NONE ) { /* turn on ClassFileLoadHook */ jvmtierror = (*jvmtienv)->SetEventNotificationMode( jvmtienv, JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL /* all threads */); check_phase_ret_false(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } return (jvmtierror == JVMTI_ERROR_NONE); } /** * Check if the can_redefine_classes capability is available. */ void checkCapabilities(JPLISAgent * agent) { jvmtiEnv * jvmtienv = jvmti(agent); jvmtiCapabilities potentialCapabilities; jvmtiError jvmtierror; memset(&potentialCapabilities, 0, sizeof(potentialCapabilities)); jvmtierror = (*jvmtienv)->GetPotentialCapabilities(jvmtienv, &potentialCapabilities); check_phase_ret(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if ( jvmtierror == JVMTI_ERROR_NONE ) { if ( potentialCapabilities.can_redefine_classes == 1 ) { agent->mRedefineAvailable = JNI_TRUE; } if ( potentialCapabilities.can_set_native_method_prefix == 1 ) { agent->mNativeMethodPrefixAvailable = JNI_TRUE; } } } /** * Enable native method prefix in one JVM TI environment */ void enableNativeMethodPrefixCapability(jvmtiEnv * jvmtienv) { jvmtiCapabilities desiredCapabilities; jvmtiError jvmtierror; jvmtierror = (*jvmtienv)->GetCapabilities(jvmtienv, &desiredCapabilities); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); desiredCapabilities.can_set_native_method_prefix = 1; jvmtierror = (*jvmtienv)->AddCapabilities(jvmtienv, &desiredCapabilities); check_phase_ret(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } /** * Add the can_set_native_method_prefix capability */ void addNativeMethodPrefixCapability(JPLISAgent * agent) { if (agent->mNativeMethodPrefixAvailable && !agent->mNativeMethodPrefixAdded) { jvmtiEnv * jvmtienv = agent->mNormalEnvironment.mJVMTIEnv; enableNativeMethodPrefixCapability(jvmtienv); jvmtienv = agent->mRetransformEnvironment.mJVMTIEnv; if (jvmtienv != NULL) { enableNativeMethodPrefixCapability(jvmtienv); } agent->mNativeMethodPrefixAdded = JNI_TRUE; } } /** * Add the can_maintain_original_method_order capability (for testing) */ void addOriginalMethodOrderCapability(JPLISAgent * agent) { jvmtiEnv * jvmtienv = jvmti(agent); jvmtiCapabilities desiredCapabilities; jvmtiError jvmtierror; jvmtierror = (*jvmtienv)->GetCapabilities(jvmtienv, &desiredCapabilities); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); desiredCapabilities.can_maintain_original_method_order = 1; jvmtierror = (*jvmtienv)->AddCapabilities(jvmtienv, &desiredCapabilities); check_phase_ret(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } /** * Add the can_redefine_classes capability */ void addRedefineClassesCapability(JPLISAgent * agent) { jvmtiEnv * jvmtienv = jvmti(agent); jvmtiCapabilities desiredCapabilities; jvmtiError jvmtierror; if (agent->mRedefineAvailable && !agent->mRedefineAdded) { jvmtierror = (*jvmtienv)->GetCapabilities(jvmtienv, &desiredCapabilities); /* can be called from any phase */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE); desiredCapabilities.can_redefine_classes = 1; jvmtierror = (*jvmtienv)->AddCapabilities(jvmtienv, &desiredCapabilities); check_phase_ret(jvmtierror); /* * With mixed premain/agentmain agents then it's possible that the * capability was potentially available in the onload phase but * subsequently unavailable in the live phase. */ jplis_assert(jvmtierror == JVMTI_ERROR_NONE || jvmtierror == JVMTI_ERROR_NOT_AVAILABLE); if (jvmtierror == JVMTI_ERROR_NONE) { agent->mRedefineAdded = JNI_TRUE; } } } /* * Support for the JVMTI callbacks */ void transformClassFile( JPLISAgent * agent, JNIEnv * jnienv, jobject loaderObject, const char* name, jclass classBeingRedefined, jobject protectionDomain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data, jboolean is_retransformer) { jboolean errorOutstanding = JNI_FALSE; jstring classNameStringObject = NULL; jarray classFileBufferObject = NULL; jarray transformedBufferObject = NULL; jsize transformedBufferSize = 0; unsigned char * resultBuffer = NULL; jboolean shouldRun = JNI_FALSE; /* only do this if we aren't already in the middle of processing a class on this thread */ shouldRun = tryToAcquireReentrancyToken( jvmti(agent), NULL); /* this thread */ if ( shouldRun ) { /* first marshall all the parameters */ classNameStringObject = (*jnienv)->NewStringUTF(jnienv, name); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't create name string"); if ( !errorOutstanding ) { classFileBufferObject = (*jnienv)->NewByteArray(jnienv, class_data_len); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't create byte arrau"); } if ( !errorOutstanding ) { jbyte * typedBuffer = (jbyte *) class_data; /* nasty cast, dumb JNI interface, const missing */ /* The sign cast is safe. The const cast is dumb. */ (*jnienv)->SetByteArrayRegion( jnienv, classFileBufferObject, 0, class_data_len, typedBuffer); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't set byte array region"); } /* now call the JPL agents to do the transforming */ /* potential future optimization: may want to skip this if there are none */ if ( !errorOutstanding ) { jplis_assert(agent->mInstrumentationImpl != NULL); jplis_assert(agent->mTransform != NULL); transformedBufferObject = (*jnienv)->CallObjectMethod( jnienv, agent->mInstrumentationImpl, agent->mTransform, loaderObject, classNameStringObject, classBeingRedefined, protectionDomain, classFileBufferObject, is_retransformer); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "transform method call failed"); } /* Finally, unmarshall the parameters (if someone touched the buffer, tell the JVM) */ if ( !errorOutstanding ) { if ( transformedBufferObject != NULL ) { transformedBufferSize = (*jnienv)->GetArrayLength( jnienv, transformedBufferObject); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't get array length"); if ( !errorOutstanding ) { /* allocate the response buffer with the JVMTI allocate call. * This is what the JVMTI spec says to do for Class File Load hook responses */ jvmtiError allocError = (*(jvmti(agent)))->Allocate(jvmti(agent), transformedBufferSize, &resultBuffer); errorOutstanding = (allocError != JVMTI_ERROR_NONE); jplis_assert_msg(!errorOutstanding, "can't allocate result buffer"); } if ( !errorOutstanding ) { (*jnienv)->GetByteArrayRegion( jnienv, transformedBufferObject, 0, transformedBufferSize, (jbyte *) resultBuffer); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't get byte array region"); /* in this case, we will not return the buffer to the JVMTI, * so we need to deallocate it ourselves */ if ( errorOutstanding ) { deallocate( jvmti(agent), (void*)resultBuffer); } } if ( !errorOutstanding ) { *new_class_data_len = (transformedBufferSize); *new_class_data = resultBuffer; } } } /* release the token */ releaseReentrancyToken( jvmti(agent), NULL); /* this thread */ } return; } /* * Misc. internal utilities. */ /* * The only checked exceptions we can throw are ClassNotFoundException and * UnmodifiableClassException. All others map to InternalError. */ jthrowable redefineClassMapper( JNIEnv * jnienv, jthrowable throwableToMap) { jthrowable mappedThrowable = NULL; jplis_assert(isSafeForJNICalls(jnienv)); jplis_assert(!isUnchecked(jnienv, throwableToMap)); if ( isInstanceofClassName( jnienv, throwableToMap, "java/lang/ClassNotFoundException") ) { mappedThrowable = throwableToMap; } else { if ( isInstanceofClassName( jnienv, throwableToMap, "java/lang/instrument/UnmodifiableClassException")) { mappedThrowable = throwableToMap; } else { jstring message = NULL; message = getMessageFromThrowable(jnienv, throwableToMap); mappedThrowable = createInternalError(jnienv, message); } } jplis_assert(isSafeForJNICalls(jnienv)); return mappedThrowable; } jobjectArray getObjectArrayFromClasses(JNIEnv* jnienv, jclass* classes, jint classCount) { jclass classArrayClass = NULL; jobjectArray localArray = NULL; jint classIndex = 0; jboolean errorOccurred = JNI_FALSE; /* get the class array class */ classArrayClass = (*jnienv)->FindClass(jnienv, "java/lang/Class"); errorOccurred = checkForThrowable(jnienv); if (!errorOccurred) { jplis_assert_msg(classArrayClass != NULL, "FindClass returned null class"); /* create the array for the classes */ localArray = (*jnienv)->NewObjectArray(jnienv, classCount, classArrayClass, NULL); errorOccurred = checkForThrowable(jnienv); if (!errorOccurred) { jplis_assert_msg(localArray != NULL, "NewObjectArray returned null array"); /* now copy refs to all the classes and put them into the array */ for (classIndex = 0; classIndex < classCount; classIndex++) { /* put class into array */ (*jnienv)->SetObjectArrayElement(jnienv, localArray, classIndex, classes[classIndex]); errorOccurred = checkForThrowable(jnienv); if (errorOccurred) { localArray = NULL; break; } } } } return localArray; } /* Return the environment with the retransformation capability. * Create it if it doesn't exist. * Return NULL if it can't be created. */ jvmtiEnv * retransformableEnvironment(JPLISAgent * agent) { jvmtiEnv * retransformerEnv = NULL; jint jnierror = JNI_OK; jvmtiCapabilities desiredCapabilities; jvmtiEventCallbacks callbacks; jvmtiError jvmtierror; if (agent->mRetransformEnvironment.mJVMTIEnv != NULL) { return agent->mRetransformEnvironment.mJVMTIEnv; } jnierror = (*agent->mJVM)->GetEnv( agent->mJVM, (void **) &retransformerEnv, JVMTI_VERSION); if ( jnierror != JNI_OK ) { return NULL; } jvmtierror = (*retransformerEnv)->GetCapabilities(retransformerEnv, &desiredCapabilities); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); desiredCapabilities.can_retransform_classes = 1; if (agent->mNativeMethodPrefixAdded) { desiredCapabilities.can_set_native_method_prefix = 1; } jvmtierror = (*retransformerEnv)->AddCapabilities(retransformerEnv, &desiredCapabilities); if (jvmtierror != JVMTI_ERROR_NONE) { /* cannot get the capability, dispose of the retransforming environment */ jvmtierror = (*retransformerEnv)->DisposeEnvironment(retransformerEnv); jplis_assert(jvmtierror == JVMTI_ERROR_NOT_AVAILABLE); return NULL; } memset(&callbacks, 0, sizeof(callbacks)); callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; jvmtierror = (*retransformerEnv)->SetEventCallbacks(retransformerEnv, &callbacks, sizeof(callbacks)); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if (jvmtierror == JVMTI_ERROR_NONE) { // install the retransforming environment agent->mRetransformEnvironment.mJVMTIEnv = retransformerEnv; agent->mRetransformEnvironment.mIsRetransformer = JNI_TRUE; // Make it for ClassFileLoadHook handling jvmtierror = (*retransformerEnv)->SetEnvironmentLocalStorage( retransformerEnv, &(agent->mRetransformEnvironment)); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if (jvmtierror == JVMTI_ERROR_NONE) { return retransformerEnv; } } return NULL; } /* * Underpinnings for native methods */ jboolean isModifiableClass(JNIEnv * jnienv, JPLISAgent * agent, jclass clazz) { jvmtiEnv * jvmtienv = jvmti(agent); jvmtiError jvmtierror; jboolean is_modifiable = JNI_FALSE; jvmtierror = (*jvmtienv)->IsModifiableClass( jvmtienv, clazz, &is_modifiable); check_phase_ret_false(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); return is_modifiable; } jboolean isRetransformClassesSupported(JNIEnv * jnienv, JPLISAgent * agent) { return agent->mRetransformEnvironment.mIsRetransformer; } void setHasRetransformableTransformers(JNIEnv * jnienv, JPLISAgent * agent, jboolean has) { jvmtiEnv * retransformerEnv = retransformableEnvironment(agent); jvmtiError jvmtierror; jplis_assert(retransformerEnv != NULL); jvmtierror = (*retransformerEnv)->SetEventNotificationMode( retransformerEnv, has? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL /* all threads */); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } void retransformClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classes) { jvmtiEnv * retransformerEnv = retransformableEnvironment(agent); jboolean errorOccurred = JNI_FALSE; jvmtiError errorCode = JVMTI_ERROR_NONE; jsize numClasses = 0; jclass * classArray = NULL; /* This is supposed to be checked by caller, but just to be sure */ if (retransformerEnv == NULL) { jplis_assert(retransformerEnv != NULL); errorOccurred = JNI_TRUE; errorCode = JVMTI_ERROR_MUST_POSSESS_CAPABILITY; } /* This was supposed to be checked by caller too */ if (!errorOccurred && classes == NULL) { jplis_assert(classes != NULL); errorOccurred = JNI_TRUE; errorCode = JVMTI_ERROR_NULL_POINTER; } if (!errorOccurred) { numClasses = (*jnienv)->GetArrayLength(jnienv, classes); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (!errorOccurred && numClasses == 0) { jplis_assert(numClasses != 0); errorOccurred = JNI_TRUE; errorCode = JVMTI_ERROR_NULL_POINTER; } } if (!errorOccurred) { classArray = (jclass *) allocate(retransformerEnv, numClasses * sizeof(jclass)); errorOccurred = (classArray == NULL); jplis_assert(!errorOccurred); if (errorOccurred) { errorCode = JVMTI_ERROR_OUT_OF_MEMORY; } } if (!errorOccurred) { jint index; for (index = 0; index < numClasses; index++) { classArray[index] = (*jnienv)->GetObjectArrayElement(jnienv, classes, index); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } if (classArray[index] == NULL) { jplis_assert(classArray[index] != NULL); errorOccurred = JNI_TRUE; errorCode = JVMTI_ERROR_NULL_POINTER; break; } } } if (!errorOccurred) { errorCode = (*retransformerEnv)->RetransformClasses(retransformerEnv, numClasses, classArray); errorOccurred = (errorCode != JVMTI_ERROR_NONE); } /* Give back the buffer if we allocated it. Throw any exceptions after. */ if (classArray != NULL) { deallocate(retransformerEnv, (void*)classArray); } if (errorCode != JVMTI_ERROR_NONE) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, errorCode); } mapThrownThrowableIfNecessary(jnienv, redefineClassMapper); } /* * Java code must not call this with a null list or a zero-length list. */ void redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) { jvmtiEnv* jvmtienv = jvmti(agent); jboolean errorOccurred = JNI_FALSE; jclass classDefClass = NULL; jmethodID getDefinitionClassMethodID = NULL; jmethodID getDefinitionClassFileMethodID = NULL; jvmtiClassDefinition* classDefs = NULL; jsize numDefs = 0; jplis_assert(classDefinitions != NULL); numDefs = (*jnienv)->GetArrayLength(jnienv, classDefinitions); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (!errorOccurred) { jplis_assert(numDefs > 0); /* get method IDs for methods to call on class definitions */ classDefClass = (*jnienv)->FindClass(jnienv, "java/lang/instrument/ClassDefinition"); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); } if (!errorOccurred) { getDefinitionClassMethodID = (*jnienv)->GetMethodID( jnienv, classDefClass, "getDefinitionClass", "()Ljava/lang/Class;"); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); } if (!errorOccurred) { getDefinitionClassFileMethodID = (*jnienv)->GetMethodID( jnienv, classDefClass, "getDefinitionClassFile", "()[B"); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); } if (!errorOccurred) { classDefs = (jvmtiClassDefinition *) allocate( jvmtienv, numDefs * sizeof(jvmtiClassDefinition)); errorOccurred = (classDefs == NULL); jplis_assert(!errorOccurred); if ( errorOccurred ) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, JVMTI_ERROR_OUT_OF_MEMORY); } else { jint i; for (i = 0; i < numDefs; i++) { jclass classDef = NULL; jbyteArray targetFile = NULL; classDef = (*jnienv)->GetObjectArrayElement(jnienv, classDefinitions, i); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } classDefs[i].klass = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassMethodID); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } targetFile = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassFileMethodID); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } classDefs[i].class_bytes = (unsigned char*)(*jnienv)->GetByteArrayElements(jnienv, targetFile, NULL); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } classDefs[i].class_byte_count = (*jnienv)->GetArrayLength(jnienv, targetFile); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } } if (!errorOccurred) { jvmtiError errorCode = JVMTI_ERROR_NONE; errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs); check_phase_blob_ret(errorCode, deallocate(jvmtienv, (void*)classDefs)); errorOccurred = (errorCode != JVMTI_ERROR_NONE); if ( errorOccurred ) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, errorCode); } } /* Give back the buffer if we allocated it. */ deallocate(jvmtienv, (void*)classDefs); } } mapThrownThrowableIfNecessary(jnienv, redefineClassMapper); } /* Cheesy sharing. ClassLoader may be null. */ jobjectArray commonGetClassList( JNIEnv * jnienv, JPLISAgent * agent, jobject classLoader, ClassListFetcher fetcher) { jvmtiEnv * jvmtienv = jvmti(agent); jboolean errorOccurred = JNI_FALSE; jvmtiError jvmtierror = JVMTI_ERROR_NONE; jint classCount = 0; jclass * classes = NULL; jobjectArray localArray = NULL; /* retrieve the classes from the JVMTI agent */ jvmtierror = (*fetcher)( jvmtienv, classLoader, &classCount, &classes); check_phase_ret_blob(jvmtierror, localArray); errorOccurred = (jvmtierror != JVMTI_ERROR_NONE); jplis_assert(!errorOccurred); if ( errorOccurred ) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, jvmtierror); } else { localArray = getObjectArrayFromClasses( jnienv, classes, classCount); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); /* do this whether or not we saw a problem */ deallocate(jvmtienv, (void*)classes); } mapThrownThrowableIfNecessary(jnienv, mapAllCheckedToInternalErrorMapper); return localArray; } jvmtiError getAllLoadedClassesClassListFetcher( jvmtiEnv * jvmtienv, jobject classLoader, jint * classCount, jclass ** classes) { return (*jvmtienv)->GetLoadedClasses(jvmtienv, classCount, classes); } jobjectArray getAllLoadedClasses(JNIEnv * jnienv, JPLISAgent * agent) { return commonGetClassList( jnienv, agent, NULL, getAllLoadedClassesClassListFetcher); } jvmtiError getInitiatedClassesClassListFetcher( jvmtiEnv * jvmtienv, jobject classLoader, jint * classCount, jclass ** classes) { return (*jvmtienv)->GetClassLoaderClasses(jvmtienv, classLoader, classCount, classes); } jobjectArray getInitiatedClasses(JNIEnv * jnienv, JPLISAgent * agent, jobject classLoader) { return commonGetClassList( jnienv, agent, classLoader, getInitiatedClassesClassListFetcher); } jlong getObjectSize(JNIEnv * jnienv, JPLISAgent * agent, jobject objectToSize) { jvmtiEnv * jvmtienv = jvmti(agent); jlong objectSize = -1; jvmtiError jvmtierror = JVMTI_ERROR_NONE; jvmtierror = (*jvmtienv)->GetObjectSize(jvmtienv, objectToSize, &objectSize); check_phase_ret_0(jvmtierror); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if ( jvmtierror != JVMTI_ERROR_NONE ) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, jvmtierror); } mapThrownThrowableIfNecessary(jnienv, mapAllCheckedToInternalErrorMapper); return objectSize; } void appendToClassLoaderSearch(JNIEnv * jnienv, JPLISAgent * agent, jstring jarFile, jboolean isBootLoader) { jvmtiEnv * jvmtienv = jvmti(agent); jboolean errorOutstanding; jvmtiError jvmtierror; const char* utf8Chars; jsize utf8Len; jboolean isCopy; char platformChars[MAXPATHLEN]; int platformLen; utf8Len = (*jnienv)->GetStringUTFLength(jnienv, jarFile); errorOutstanding = checkForAndClearThrowable(jnienv); if (!errorOutstanding) { utf8Chars = (*jnienv)->GetStringUTFChars(jnienv, jarFile, &isCopy); errorOutstanding = checkForAndClearThrowable(jnienv); if (!errorOutstanding && utf8Chars != NULL) { /* * JVMTI spec'ed to use modified UTF8. At this time this is not implemented * the platform encoding is used. */ platformLen = convertUft8ToPlatformString((char*)utf8Chars, utf8Len, platformChars, MAXPATHLEN); if (platformLen < 0) { createAndThrowInternalError(jnienv); return; } (*jnienv)->ReleaseStringUTFChars(jnienv, jarFile, utf8Chars); errorOutstanding = checkForAndClearThrowable(jnienv); if (!errorOutstanding) { if (isBootLoader) { jvmtierror = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, platformChars); } else { jvmtierror = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, platformChars); } check_phase_ret(jvmtierror); if ( jvmtierror != JVMTI_ERROR_NONE ) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, jvmtierror); } } } } mapThrownThrowableIfNecessary(jnienv, mapAllCheckedToInternalErrorMapper); } /* * Set the prefixes used to wrap native methods (so they can be instrumented). * Each transform can set a prefix, any that have been set come in as prefixArray. * Convert them in native strings in a native array then call JVM TI. * One a given call, this function handles either the prefixes for retransformable * transforms or for normal transforms. */ void setNativeMethodPrefixes(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray prefixArray, jboolean isRetransformable) { jvmtiEnv* jvmtienv; jvmtiError err = JVMTI_ERROR_NONE; jsize arraySize; jboolean errorOccurred = JNI_FALSE; jplis_assert(prefixArray != NULL); if (isRetransformable) { jvmtienv = agent->mRetransformEnvironment.mJVMTIEnv; } else { jvmtienv = agent->mNormalEnvironment.mJVMTIEnv; } arraySize = (*jnienv)->GetArrayLength(jnienv, prefixArray); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (!errorOccurred) { /* allocate the native to hold the native prefixes */ const char** prefixes = (const char**) allocate(jvmtienv, arraySize * sizeof(char*)); /* since JNI ReleaseStringUTFChars needs the jstring from which the native * string was allocated, we store them in a parallel array */ jstring* originForRelease = (jstring*) allocate(jvmtienv, arraySize * sizeof(jstring)); errorOccurred = (prefixes == NULL || originForRelease == NULL); jplis_assert(!errorOccurred); if ( errorOccurred ) { createAndThrowThrowableFromJVMTIErrorCode(jnienv, JVMTI_ERROR_OUT_OF_MEMORY); } else { jint inx = 0; jint i; for (i = 0; i < arraySize; i++) { jstring prefixStr = NULL; const char* prefix; jsize prefixLen; jboolean isCopy; prefixStr = (jstring) ((*jnienv)->GetObjectArrayElement(jnienv, prefixArray, i)); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } if (prefixStr == NULL) { continue; } prefixLen = (*jnienv)->GetStringUTFLength(jnienv, prefixStr); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (errorOccurred) { break; } if (prefixLen > 0) { prefix = (*jnienv)->GetStringUTFChars(jnienv, prefixStr, &isCopy); errorOccurred = checkForThrowable(jnienv); jplis_assert(!errorOccurred); if (!errorOccurred && prefix != NULL) { prefixes[inx] = prefix; originForRelease[inx] = prefixStr; ++inx; } } } err = (*jvmtienv)->SetNativeMethodPrefixes(jvmtienv, inx, (char**)prefixes); /* can be called from any phase */ jplis_assert(err == JVMTI_ERROR_NONE); for (i = 0; i < inx; i++) { (*jnienv)->ReleaseStringUTFChars(jnienv, originForRelease[i], prefixes[i]); } } deallocate(jvmtienv, (void*)prefixes); deallocate(jvmtienv, (void*)originForRelease); } }