JVMTI Agent 工作原理及核心原始碼分析

猿碼道發表於2018-05-26

0 前言

前一節講述了基於JVMTI如何實現Agent,還有一種是基於Java Instrument API實現Agent,可以在Java程式碼層面編寫Agent程式碼,而非基於C++/C的程式碼,具體使用可參考《Java Instrument 功能使用及原理》

-javaagent:為開頭的預設為instrument的agent;

那麼以上這兩種Agent實現方式,又是在JVMTI原始碼中如何執行工作呢?

1 初始化 Agent

在JVM啟動時,會讀取JVM命令列引數 -agentlib -agentpath -javaagent並構建了Agent Library連結串列初始化 Agent 程式碼如下

if (match_option(option, "-agentlib:", &tail) || (is_absolute_path = match_option(option, "-agentpath:", &tail))) {  
  if(tail != NULL) {  
    const char* pos = strchr(tail, '=');  
    size_t len = (pos == NULL) ? strlen(tail) : pos - tail;  
    char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1), tail, len);  
    name[len] = '\0';  
    char *options = NULL;  
    if(pos != NULL) {  
      options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(pos + 1) + 1), pos + 1);  
    }  
    if ((strcmp(name, "hprof") == 0) || (strcmp(name, "jdwp") == 0)) {  
      warning("profiling and debugging agents are not supported with Kernel VM");    
    } else  if
      // JVMTI_KERNEL 構建Agent Library連結串列 
      add_init_agent(name, options, is_absolute_path);  
    } 
} else if (match_option(option, "-javaagent:", &tail)) {
  // -javaagent   
  if(tail != NULL) {  
    char *options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(tail) + 1), tail);  
    // 構建Agent Library連結串列
    add_init_agent("instrument", options, false);  
  }  
  // -Xnoclassgc  
}
複製程式碼

2 載入Agent連結庫

在啟動JVM create_vm時,對agent連結串列中的每個agent庫,載入所指定的動態庫, 並呼叫裡面的Agent_OnLoad方法,比如:對於Java Instrument Agent載入就是對libinstrument的動態庫instrument.so載入

// Create agents for -agentlib:  -agentpath:  and converted -Xrun  
void Threads::create_vm_init_agents() {  
  extern struct JavaVM_ main_vm;  
  AgentLibrary* agent;  
  JvmtiExport::enter_onload_phase();  
  for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {  
    OnLoadEntry_t  on_load_entry = lookup_agent_on_load(agent);    
    if (on_load_entry != NULL) {  
      // 呼叫 Agent_OnLoad 函式  
      jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);  
      if (err != JNI_OK) {  
        vm_exit_during_initialization("agent library failed to init", agent->name());  
      }  
    } else {  
      vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());  
    }  
  }  
  JvmtiExport::enter_primordial_phase();  
}
複製程式碼

3 建立 Instrument JPLISAgent

在方法Agent_OnLoad中建立一個新的 JPLISAgent(Java Programming Language Instrumentation Services Agent),初始化了類和包裡的配置檔案,並且同時從Vm環境中獲取了 jvmtiEnv 的環境

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {  
    JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;  
    jint                     result     = JNI_OK;  
    JPLISAgent *             agent      = NULL;
    // 建立一個新的JPLISAgent物件  
    initerror = createNewJPLISAgent(vm, &agent);  
    if ( initerror == JPLIS_INIT_ERROR_NONE ) {  
        if (parseArgumentTail(tail, &jarfile, &options) != 0) {  
            fprintf(stderr, "-javaagent: memory allocation failure.\n");  
            return JNI_ERR;  
        }  
        attributes = readAttributes(jarfile);  
        if (attributes == NULL) {  
            fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);  
            free(jarfile);  
            if (options != NULL) free(options);  
            return JNI_ERR;  
        }  
        premainClass = getAttribute(attributes, "Premain-Class");  
        if (premainClass == NULL) {  
            fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", jarfile);  
            free(jarfile);  
            if (options != NULL) free(options);  
            freeAttributes(attributes);  
            return JNI_ERR;  
        }  
        /* 
         * Add to the jarfile 把jar檔案追加到agent的classpath中。
         */  
        appendClassPath(agent, jarfile);  
        ……  
}  
複製程式碼

在程式碼中,可以看到在 讀取jar的配置檔案MANIFEST裡Premain-Class,並且把jar檔案追加到agent的class path中

4 JVMTI 回撥介面註冊與執行

以下是JVMTI的一些回撥介面,通過這些回撥介面設定回撥函式指標:

typedef struct {
    /* 50 : VM Initialization Event */  
    jvmtiEventVMInit VMInit;   
    /* 51 : VM Death Event */  
    jvmtiEventVMDeath VMDeath; 
    /* 52 : Thread Start */  
    jvmtiEventThreadStart ThreadStart;
    /* 53 : Thread End */  
    jvmtiEventThreadEnd ThreadEnd;  
    /* 54 : Class File Load Hook */  
    jvmtiEventClassFileLoadHook ClassFileLoadHook;
    /* 55 : Class Load */  
    jvmtiEventClassLoad ClassLoad; 
    /* 56 : Class Prepare */  
    jvmtiEventClassPrepare ClassPrepare;
    /* 57 : VM Start Event */  
    jvmtiEventVMStart VMStart;
    /* 58 : Exception */  
    jvmtiEventException Exception;
    /* 59 : Exception Catch */  
    jvmtiEventExceptionCatch ExceptionCatch; 
    /* 60 : Single Step */  
    jvmtiEventSingleStep SingleStep;
    /* 61 : Frame Pop */  
    jvmtiEventFramePop FramePop;
    /* 62 : Breakpoint */  
    jvmtiEventBreakpoint Breakpoint; 
    /* 63 : Field Access */  
    jvmtiEventFieldAccess FieldAccess;
    /* 64 : Field Modification */  
    jvmtiEventFieldModification FieldModification; 
    /* 65 : Method Entry */  
    jvmtiEventMethodEntry MethodEntry;
    /* 66 : Method Exit */  
    jvmtiEventMethodExit MethodExit;
    /* 67 : Native Method Bind */  
    jvmtiEventNativeMethodBind NativeMethodBind;
    /* 68 : Compiled Method Load */  
    jvmtiEventCompiledMethodLoad CompiledMethodLoad;
    /* 69 : Compiled Method Unload */  
    jvmtiEventCompiledMethodUnload CompiledMethodUnload; 
    /* 70 : Dynamic Code Generated */  
    jvmtiEventDynamicCodeGenerated DynamicCodeGenerated; 
    /* 71 : Data Dump Request */  
    jvmtiEventDataDumpRequest DataDumpRequest;
    /* 72 */  
    jvmtiEventReserved reserved72;
    /* 73 : Monitor Wait */  
    jvmtiEventMonitorWait MonitorWait;
    /* 74 : Monitor Waited */  
    jvmtiEventMonitorWaited MonitorWaited;
    /* 75 : Monitor Contended Enter */  
    jvmtiEventMonitorContendedEnter MonitorContendedEnter;
    /* 76 : Monitor Contended Entered */  
    jvmtiEventMonitorContendedEntered MonitorContendedEntered;
    /* 77 */  
    jvmtiEventReserved reserved77;
    /* 78 */  
    jvmtiEventReserved reserved78; 
    /* 79 */  
    jvmtiEventReserved reserved79; 
    /* 80 : Resource Exhausted */  
    jvmtiEventResourceExhausted ResourceExhausted;
    /* 81 : Garbage Collection Start */  
    jvmtiEventGarbageCollectionStart GarbageCollectionStart;
    /* 82 : Garbage Collection Finish */  
    jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
    /* 83 : Object Free */  
    jvmtiEventObjectFree ObjectFree;
    /* 84 : VM Object Allocation */  
    jvmtiEventVMObjectAlloc VMObjectAlloc;  
} jvmtiEventCallbacks;  
複製程式碼

4.1 執行jvmtiEventVMInit的回撥函式

虛擬機器在建立create_vm的時候,初始化了JVMTI環境後會執行JvmtiExport::post_vm_initialized(); 方法,程式碼如下:

void JvmtiExport::post_vm_initialized() {  
  EVT_TRIG_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Trg VM init event triggered" ));  
  // can now enable events  
  JvmtiEventController::vm_init();  
  JvmtiEnvIterator it;  
  for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {  
    if (env->is_enabled(JVMTI_EVENT_VM_INIT)) {  
      EVT_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Evt VM init event sent" ));  
      JavaThread *thread  = JavaThread::current();  
      JvmtiThreadEventMark jem(thread);  
      JvmtiJavaThreadEventTransition jet(thread);  
      jvmtiEventVMInit callback = env->callbacks()->VMInit;  
      if (callback != NULL) {
         // 呼叫了VMInit的回撥函式  
         (*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread());  
      }  
    }  
  }  
}  
複製程式碼

4.2 執行jvmtiEventClassFileLoadHook的回撥函式

鉤子方法是jvmtiEventClassFileLoadHook的回撥方法,程式碼在classFileParser的檔案中

instanceKlassHandle ClassFileParser::parseClassFile(symbolHandle name, Handle class_loader,Handle protection_domain, KlassHandle host_klass, GrowableArray<Handle>* cp_patches, symbolHandle& parsed_name,bool verify, TRAPS) {  
  ……  
  if (JvmtiExport::should_post_class_file_load_hook()) {  
     unsigned char* ptr = cfs->buffer();  
     unsigned char* end_ptr = cfs->buffer() + cfs->length();  
     JvmtiExport::post_class_file_load_hook(name, class_loader, protection_domain,  
                                           &ptr, &end_ptr,  
                                           &cached_class_file_bytes,  
                                           &cached_class_file_length);  
     if (ptr != cfs->buffer()) {  
        // JVMTI agent has modified class file data.  
        // Set new class file stream using JVMTI agent modified  
        // class file data.  
        cfs = new ClassFileStream(ptr, end_ptr - ptr, cfs->source());  
        set_stream(cfs);  
     }  
  }  
  …  
}
複製程式碼

jvmtiexport::post_class_file_load_hook函式最後 呼叫了post_to_env()函式

void post_to_env(JvmtiEnv* env, bool caching_needed) {  
   unsigned char *new_data = NULL;  
   jint new_len = 0;  
   JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader,  
                                    _h_protection_domain,  
                                    _h_class_being_redefined);  
  
   JvmtiJavaThreadEventTransition jet(_thread);  
  
   JNIEnv* jni_env =  (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)? NULL : jem.jni_env();  
  
   jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook;  
  
   if (callback != NULL) {  
      (*callback)(env->jvmti_external(), jni_env,  
            jem.class_being_redefined(),  
            jem.jloader(), jem.class_name(),  
            jem.protection_domain(),  
            _curr_len, _curr_data,  
            &new_len, &new_data);
   }
   ......  
}  
複製程式碼

函式中呼叫了jvmtiEventClassFileLoadHook的回撥函式,也就是剛才在結構體中定義的jvmtiEventCallbacks。鉤子函式eventHandlerClassFileLoadHook

void JNICALL eventHandlerClassFileLoadHook(  jvmtiEnv *              jvmtienv,  
                                JNIEnv *                jnienv,  
                                jclass                  class_being_redefined,  
                                jobject                 loader,  
                                const char*             name,  
                                jobject                 protectionDomain,  
                                jint                    class_data_len,  
                                const unsigned char*    class_data,  
                                jint*                   new_class_data_len,  
                                unsigned char**         new_class_data) {  
    JPLISEnvironment * environment  = NULL;  
  
    environment = getJPLISEnvironment(jvmtienv);  
  
    /* if something is internally inconsistent (no agent), just silently return without touching the buffer */  
    if ( environment != NULL ) {  
        jthrowable outstandingException = preserveThrowable(jnienv);  
        transformClassFile(environment->mAgent,  
                            jnienv,  
                            loader,  
                            name,  
                            class_being_redefined,  
                            protectionDomain,  
                            class_data_len,  
                            class_data,  
                            new_class_data_len,  
                            new_class_data,  
                            environment->mIsRetransformer);  
        restoreThrowable(jnienv, outstandingException);  
    }  
}  
複製程式碼

重要的是transformClassFile函式,看看它究竟做了啥事情:

transformedBufferObject = (*jnienv)->CallObjectMethod(  
                                                jnienv,  
                                                agent->mInstrumentationImpl,  
                                                agent->mTransform,  
                                                loaderObject,  
                                                classNameStringObject,  
                                                classBeingRedefined,  
                                                protectionDomain,  
                                                classFileBufferObject,  
                                                is_retransformer);  
複製程式碼

也就是呼叫了InstrumentationImpl裡的transform方法,在InstrumentationImpl類裡通過TransformerManager的transform的方法最終呼叫我們自定義的MyTransformer的類的transform方法

    private byte[] transform(ClassLoader var1, String var2, Class var3, ProtectionDomain var4, byte[] var5, boolean var6) {
        TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager;
        return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5);
    }
複製程式碼

4.3 註冊鉤子函式jvmtiEventClassFileLoadHook

如上,那麼鉤子函式jvmtiEventClassFileLoadHook是何時註冊的,回到剛才建立新的JPLISAgent程式碼中

JPLISInitializationError  createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {  
    JPLISInitializationError initerror       = JPLIS_INIT_ERROR_NONE;  
    jvmtiEnv *               jvmtienv        = NULL;  
    jint                     jnierror        = JNI_OK;  
    *agent_ptr = NULL;  
    jnierror = (*vm)->GetEnv(vm,(void **) &jvmtienv,JVMTI_VERSION);  
    if (jnierror != JNI_OK) {  
        initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;  
    } else {  
        JPLISAgent * agent = allocateJPLISAgent(jvmtienv);  
        if (agent == NULL) {  
            initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;  
        } else {  
            initerror = initializeJPLISAgent(agent, vm, jvmtienv);  
            if (initerror == JPLIS_INIT_ERROR_NONE) {  
                *agent_ptr = agent;  
            } else {  
                deallocateJPLISAgent(jvmtienv, agent);  
            }  
        }  
        /* don't leak envs */  
        if ( initerror != JPLIS_INIT_ERROR_NONE ) {  
            jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment(jvmtienv);  
            jplis_assert(jvmtierror == JVMTI_ERROR_NONE);  
        }  
    }  
    return initerror;  
}  
複製程式碼

函式initializeJPLISAgent初始化了JPLISAgent:

JPLISInitializationError initializeJPLISAgent(   JPLISAgent *    agent,JavaVM *        vm,jvmtiEnv *      jvmtienv) {  
   ……  
    checkCapabilities(agent);  
    jvmtierror == (*jvmtienv)->GetPhase(jvmtienv, &phase);  
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);  
    if (phase == JVMTI_PHASE_LIVE) {  
        return JPLIS_INIT_ERROR_NONE;  
    }  
    /* 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));  
  
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);  
    }  
  ……  
}  
複製程式碼

JPLISAgent裡首先 註冊了一個VMInit的初始化函式eventHandlerVMInit,跟蹤eventHandlerVMInit函式

void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv,  
                    JNIEnv * jnienv,  
                    jthread thread) {  
    JPLISEnvironment * environment  = NULL;  
    jboolean success = JNI_FALSE;  
  
    environment = getJPLISEnvironment(jvmtienv);  
  
    /* process the premain calls on the all the JPL agents */  
    if ( environment != NULL ) {  
        jthrowable outstandingException = preserveThrowable(jnienv);  
        success = processJavaStart( environment->mAgent,  
                                    jnienv);  
        restoreThrowable(jnienv, outstandingException);  
    }  
  
    /* if we fail to start cleanly, bring down the JVM */  
    if ( !success ) {  
        abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART);  
    }  
} 
複製程式碼

在processJavaStart裡

jboolean processJavaStart(JPLISAgent * agent, JNIEnv * jnienv) {  
    jboolean    result;  
    result = initializeFallbackError(jnienv);  
    jplis_assert(result);  
    if ( result ) {  
        result = createInstrumentationImpl(jnienv, agent);  
        jplis_assert(result);  
    }  
    if ( result ) {  
        result = setLivePhaseEventHandlers(agent);  
        jplis_assert(result);  
    }  
    if ( result ) {  
        result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString,agent->mPremainCaller);  
    }  
    if ( result ) {  
        deallocateCommandLineData(agent);  
    }  
    return result;  
} 
複製程式碼

在setLivePhaseEventHandler函式中註冊了,程式碼如下

callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; 
複製程式碼

5 JPLISAgent結構體

struct _JPLISAgent {  
    JavaVM *                mJVM;                   /* handle to the JVM */  
    JPLISEnvironment        mNormalEnvironment;     /* for every thing but retransform stuff */  
    JPLISEnvironment        mRetransformEnvironment;/* for retransform stuff only */  
    jobject                 mInstrumentationImpl;   /* handle to the Instrumentation instance */  
    jmethodID               mPremainCaller;         /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */  
    jmethodID               mAgentmainCaller;       /* method on the InstrumentationImpl for agents loaded via attach mechanism */  
    jmethodID               mTransform;             /* method on the InstrumentationImpl that does the class file transform */  
    jboolean                mRedefineAvailable;     /* cached answer to "does this agent support redefine" */  
    jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added */  
    jboolean                mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */  
    jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */  
    char const *            mAgentClassName;        /* agent class name */  
    char const *            mOptionsString;         /* -javaagent options string */  
};  
struct _JPLISEnvironment {  
    jvmtiEnv *              mJVMTIEnv;              /* the JVM TI environment */  
    JPLISAgent *            mAgent;                 /* corresponding agent */  
    jboolean                mIsRetransformer;       /* indicates if special environment */  
};  
複製程式碼
  1. mNormalEnvironment:agent環境;
  2. mRetransformEnvironment:retransform環境;
  3. mInstrumentationImpl:sun自己提供的instrument物件;
  4. mPremainCallersun.instrument.InstrumentationImpl.loadClassAndCallPremain方法,agent啟動時載入會被呼叫該方法;
  5. mAgentmainCallersun.instrument.InstrumentationImpl.loadClassAndCallAgentmain方法,agent attach動態載入agent的時會被呼叫該方法;
  6. mTransformsun.instrument.InstrumentationImpl.transform方法;
  7. mAgentClassName:javaagent的MANIFEST.MF裡指定的Agent-Class
  8. mOptionsString:agent初始引數;
  9. mRedefineAvailable:MANIFEST.MF裡的引數Can-Redefine-Classes:true
  10. mNativeMethodPrefixAvailable:MANIFEST.MF裡的引數Can-Set-Native-Method-Prefix:true
  11. mIsRetransformer:MANIFEST.MF裡的引數Can-Retransform-Classes:true

在startJavaAgent的方法中呼叫了啟動JPLISAgent的方式,我們來看invokeJavaAgentMainMethod

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;  
}  
複製程式碼

在函式裡,實際上是呼叫java類sun.instrument.InstrumentationImpl 類裡的方法loadClassAndCallPremain。

    private void loadClassAndCallPremain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "premain", var2);
    }

    private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "agentmain", var2);
    }
複製程式碼

繼續檢視Java的sun.instrument.InstrumentationImpl類的方法loadClassAndStartAgent:

private void loadClassAndStartAgent(String classname,  
                            String methodname,  
                            String optionsString) throws Throwable {  
        ...  
        try {  
            m = javaAgentClass.getDeclaredMethod( methodname,  
                                 new Class<?>[] {  
                                     String.class,  
                                     java.lang.instrument.Instrumentation.class  
                                 }  
                               );  
            twoArgAgent = true;  
        } catch (NoSuchMethodException x) {  
            // remember the NoSuchMethodException  
            firstExc = x;  
        }  
  
        if (m == null) {  
            // now try the declared 1-arg method  
            try {  
                m = javaAgentClass.getDeclaredMethod(methodname, new Class<?>[] { String.class });  
            } catch (NoSuchMethodException x) {  
                // ignore this exception because we'll try  
                // two arg inheritance next  
            }  
        }  
  
        if (m == null) {  
            // now try the inherited 2-arg method  
            try {  
                m = javaAgentClass.getMethod( methodname,  
                                 new Class<?>[] {  
                                     String.class,  
                                     java.lang.instrument.Instrumentation.class  
                                 }  
                               );  
                twoArgAgent = true;  
            } catch (NoSuchMethodException x) {  
                // ignore this exception because we'll try  
                // one arg inheritance next  
            }  
        }  
  
        if (m == null) {  
            // finally try the inherited 1-arg method  
            try {  
                m = javaAgentClass.getMethod(methodname, new Class<?>[] { String.class });  
            } catch (NoSuchMethodException x) {  
                // none of the methods exists so we throw the  
                // first NoSuchMethodException as per 5.0  
                throw firstExc;  
            }  
        }  
  
        // the premain method should not be required to be public,  
        // make it accessible so we can call it  
        // Note: The spec says the following:  
        //     The agent class must implement a public static premain method...  
        setAccessible(m, true);  
  
        // invoke the 1 or 2-arg method  
        if (twoArgAgent) {  
            m.invoke(null, new Object[] { optionsString, this });  
        } else {  
            m.invoke(null, new Object[] { optionsString });  
        }  
  
        // don't let others access a non-public premain method  
        setAccessible(m, false);  
    } 
複製程式碼

在InstrumentationImpl的類中初始化了我們自定義的Transformer的premain方法:

public class MyInjectTransformer  implements ClassFileTransformer{  
    public static void premain(String options, Instrumentation ins) {  
        ins.addTransformer(new SQLInjectTransformer());  
    }  
      
    @Override  
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,  
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {  
        return null;  
    }  
      
}  
複製程式碼

6 工作原理及執行時序圖

6.1 啟動並建立JVM,註冊vmInit回撥函式

啟動並建立JVM

6.2 執行vmInit回撥函式,註冊jvmtiEventClassFileLoadHook回撥函式,載入並初始化 Instrument Agent

執行vmInit回撥函式,註冊jvmtiEventClassFileLoadHook回撥函式,載入並初始化 Instrument Agent

6.3 載入解析Class檔案,執行jvmtiEventClassFileLoadHook回撥函式

載入解析Class檔案,執行jvmtiEventClassFileLoadHook回撥函式

6.4 以-javaagent為例,工作原理

  1. 在JVM啟動時,通過JVM引數-javaagent,傳入agent jar,Instrument Agent被載入;
  2. 在Instrument Agent 初始化時,註冊了JVMTI初始化函式eventHandlerVMinit
  3. 在JVM啟動時,會呼叫初始化函式eventHandlerVMinit,啟動了Instrument Agent,用sun.instrument.instrumentationImpl類裡的方法loadClassAndCallPremain方法去初始化Premain-Class指定類的premain方法
  4. 初始化函式eventHandlerVMinit,註冊了class解析的ClassFileLoadHook函式
  5. 在解析Class之前,JVM呼叫JVMTI的ClassFileLoadHook函式,鉤子函式呼叫sun.instrument.instrumentationImpl類裡的transform方法,通過TransformerManager的transformer方法最終呼叫我們自定義的Transformer類的transform方法
  6. 因為位元組碼在解析Class之前改的,直接使用修改後的位元組碼的資料流替代,最後進入Class解析,對整個Class解析無影響;
  7. 重新載入Class依然重新走5-6步驟;

相關文章