| /* |
| * Copyright (c) 2003, 2011, 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * Copyright 2003 Wily Technology, Inc. |
| */ |
| |
| #include <jni.h> |
| #include <jvmti.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #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_1_1); |
| 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_1_1); |
| 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; |
| jbyteArray* targetFiles = 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 { |
| /* |
| * We have to save the targetFile values that we compute so |
| * that we can release the class_bytes arrays that are |
| * returned by GetByteArrayElements(). In case of a JNI |
| * error, we can't (easily) recompute the targetFile values |
| * and we still want to free any memory we allocated. |
| */ |
| targetFiles = (jbyteArray *) allocate(jvmtienv, |
| numDefs * sizeof(jbyteArray)); |
| errorOccurred = (targetFiles == NULL); |
| jplis_assert(!errorOccurred); |
| if ( errorOccurred ) { |
| deallocate(jvmtienv, (void*)classDefs); |
| createAndThrowThrowableFromJVMTIErrorCode(jnienv, |
| JVMTI_ERROR_OUT_OF_MEMORY); |
| } |
| else { |
| jint i, j; |
| |
| // clear classDefs so we can correctly free memory during errors |
| memset(classDefs, 0, numDefs * sizeof(jvmtiClassDefinition)); |
| |
| for (i = 0; i < numDefs; i++) { |
| jclass classDef = 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; |
| } |
| |
| targetFiles[i] = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassFileMethodID); |
| errorOccurred = checkForThrowable(jnienv); |
| jplis_assert(!errorOccurred); |
| if (errorOccurred) { |
| break; |
| } |
| |
| classDefs[i].class_byte_count = (*jnienv)->GetArrayLength(jnienv, targetFiles[i]); |
| errorOccurred = checkForThrowable(jnienv); |
| jplis_assert(!errorOccurred); |
| if (errorOccurred) { |
| break; |
| } |
| |
| /* |
| * Allocate class_bytes last so we don't have to free |
| * memory on a partial row error. |
| */ |
| classDefs[i].class_bytes = (unsigned char*)(*jnienv)->GetByteArrayElements(jnienv, targetFiles[i], NULL); |
| errorOccurred = checkForThrowable(jnienv); |
| jplis_assert(!errorOccurred); |
| if (errorOccurred) { |
| break; |
| } |
| } |
| |
| if (!errorOccurred) { |
| jvmtiError errorCode = JVMTI_ERROR_NONE; |
| errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs); |
| if (errorCode == JVMTI_ERROR_WRONG_PHASE) { |
| /* insulate caller from the wrong phase error */ |
| errorCode = JVMTI_ERROR_NONE; |
| } else { |
| errorOccurred = (errorCode != JVMTI_ERROR_NONE); |
| if ( errorOccurred ) { |
| createAndThrowThrowableFromJVMTIErrorCode(jnienv, errorCode); |
| } |
| } |
| } |
| |
| /* |
| * Cleanup memory that we allocated above. If we had a |
| * JNI error, a JVM/TI error or no errors, index 'i' |
| * tracks how far we got in processing the classDefs |
| * array. Note: ReleaseByteArrayElements() is safe to |
| * call with a JNI exception pending. |
| */ |
| for (j = 0; j < i; j++) { |
| if ((jbyte *)classDefs[j].class_bytes != NULL) { |
| (*jnienv)->ReleaseByteArrayElements(jnienv, |
| targetFiles[j], (jbyte *)classDefs[j].class_bytes, |
| 0 /* copy back and free */); |
| /* |
| * Only check for error if we didn't already have one |
| * so we don't overwrite errorOccurred. |
| */ |
| if (!errorOccurred) { |
| errorOccurred = checkForThrowable(jnienv); |
| jplis_assert(!errorOccurred); |
| } |
| } |
| } |
| deallocate(jvmtienv, (void*)targetFiles); |
| 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); |
| } |
| } |