src/java.instrument/share/native/libinstrument/JPLISAgent.c
changeset 47216 71c04702a3d5
parent 41360 241663e51880
child 47651 148b73047771
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.instrument/share/native/libinstrument/JPLISAgent.c	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1590 @@
+/*
+ * Copyright (c) 2003, 2016, 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    <jvm.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;
+    agent->mJarfile                                  = 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;
+        }
+    }
+}
+
+static jobject
+getModuleObject(jvmtiEnv*               jvmti,
+                jobject                 loaderObject,
+                const char*             cname) {
+    jvmtiError err = JVMTI_ERROR_NONE;
+    jobject moduleObject = NULL;
+
+    /* find last slash in the class name */
+    char* last_slash = (cname == NULL) ? NULL : strrchr(cname, '/');
+    int len = (last_slash == NULL) ? 0 : (int)(last_slash - cname);
+    char* pkg_name_buf = (char*)malloc(len + 1);
+
+    jplis_assert_msg(pkg_name_buf != NULL, "OOM error in native tmp buffer allocation");
+    if (last_slash != NULL) {
+        strncpy(pkg_name_buf, cname, len);
+    }
+    pkg_name_buf[len] = '\0';
+
+    err = (*jvmti)->GetNamedModule(jvmti, loaderObject, pkg_name_buf, &moduleObject);
+    free((void*)pkg_name_buf);
+    check_phase_ret_blob(err, NULL);
+    jplis_assert_msg(err == JVMTI_ERROR_NONE, "error in the JVMTI GetNamedModule");
+
+    return moduleObject;
+}
+
+/*
+ *  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 array");
+        }
+
+        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 ) {
+            jobject moduleObject = NULL;
+
+            if (classBeingRedefined == NULL) {
+                moduleObject = getModuleObject(jvmti(agent), loaderObject, name);
+            } else {
+                // Redefine or retransform, InstrumentationImpl.transform() will use
+                // classBeingRedefined.getModule() to get the module.
+            }
+            jplis_assert(agent->mInstrumentationImpl != NULL);
+            jplis_assert(agent->mTransform != NULL);
+            transformedBufferObject = (*jnienv)->CallObjectMethod(
+                                                jnienv,
+                                                agent->mInstrumentationImpl,
+                                                agent->mTransform,
+                                                moduleObject,
+                                                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);
+    }
+}