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

0 前言

前一節(jié)講述了基于JVMTI如何實現(xiàn)Agent,還有一種是基于Java Instrument API實現(xiàn)Agent唯咬,可以在Java代碼層面編寫Agent代碼,而非基于C++/C的代碼共郭,具體使用可參考《Java Instrument 功能使用及原理》

-javaagent:為開頭的默認為instrument的agent郭脂;

那么以上這兩種Agent實現(xiàn)方式稽荧,又是在JVMTI源碼中如何運行工作呢橘茉?

1 初始化 Agent

在JVM啟動時,會讀取JVM命令行參數(shù) -agentlib -agentpath -javaagent并構(gòu)建了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 構(gòu)建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);  
    // 構(gòu)建Agent Library鏈表
    add_init_agent("instrument", options, false);  
  }  
  // -Xnoclassgc  
}

2 加載Agent鏈接庫

在啟動JVM create_vm時,對agent鏈表中的每個agent庫构挤,加載所指定的動態(tài)庫髓介, 并調(diào)用里面的Agent_OnLoad方法,比如:對于Java Instrument Agent加載就是對libinstrument的動態(tài)庫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) {  
      // 調(diào)用 Agent_OnLoad 函數(shù)  
      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 創(chuàng)建 Instrument JPLISAgent

在方法Agent_OnLoad中創(chuàng)建一個新的 JPLISAgent(Java Programming Language Instrumentation Services Agent)筋现,初始化了類和包里的配置文件唐础,并且同時從Vm環(huán)境中獲取了 jvmtiEnv 的環(huán)境

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {  
    JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;  
    jint                     result     = JNI_OK;  
    JPLISAgent *             agent      = NULL;
    // 創(chuàng)建一個新的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 回調(diào)接口注冊與執(zhí)行

以下是JVMTI的一些回調(diào)接口豹绪,通過這些回調(diào)接口設置回調(diào)函數(shù)指針:

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 執(zhí)行jvmtiEventVMInit的回調(diào)函數(shù)

虛擬機在創(chuàng)建create_vm的時候,初始化了JVMTI環(huán)境后會執(zhí)行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) {
         // 調(diào)用了VMInit的回調(diào)函數(shù)  
         (*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread());  
      }  
    }  
  }  
}  

4.2 執(zhí)行jvmtiEventClassFileLoadHook的回調(diào)函數(shù)

鉤子方法是jvmtiEventClassFileLoadHook的回調(diào)方法瞒津,代碼在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函數(shù)最后 調(diào)用了post_to_env()函數(shù)

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);
   }
   ......  
}  

函數(shù)中調(diào)用了jvmtiEventClassFileLoadHook的回調(diào)函數(shù),也就是剛才在結(jié)構(gòu)體中定義的jvmtiEventCallbacks括尸。鉤子函數(shù)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函數(shù)巷蚪,看看它究竟做了啥事情:

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

也就是調(diào)用了InstrumentationImpl里的transform方法,在InstrumentationImpl類里通過TransformerManager的transform的方法最終調(diào)用我們自定義的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 注冊鉤子函數(shù)jvmtiEventClassFileLoadHook

如上屁柏,那么鉤子函數(shù)jvmtiEventClassFileLoadHook是何時注冊的,回到剛才創(chuàng)建新的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;  
}  

函數(shù)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的初始化函數(shù)eventHandlerVMInit有送,跟蹤eventHandlerVMInit函數(shù)

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函數(shù)中注冊了淌喻,代碼如下

callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; 

5 JPLISAgent結(jié)構(gòu)體

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環(huán)境;
  2. mRetransformEnvironment:retransform環(huán)境娶眷;
  3. mInstrumentationImpl:sun自己提供的instrument對象似嗤;
  4. mPremainCallersun.instrument.InstrumentationImpl.loadClassAndCallPremain方法,agent啟動時加載會被調(diào)用該方法届宠;
  5. mAgentmainCallersun.instrument.InstrumentationImpl.loadClassAndCallAgentmain方法烁落,agent attach動態(tài)加載agent的時會被調(diào)用該方法;
  6. mTransformsun.instrument.InstrumentationImpl.transform方法豌注;
  7. mAgentClassName:javaagent的MANIFEST.MF里指定的Agent-Class伤塌;
  8. mOptionsString:agent初始參數(shù);
  9. mRedefineAvailable:MANIFEST.MF里的參數(shù)Can-Redefine-Classes:true轧铁;
  10. mNativeMethodPrefixAvailable:MANIFEST.MF里的參數(shù)Can-Set-Native-Method-Prefix:true每聪;
  11. mIsRetransformer:MANIFEST.MF里的參數(shù)Can-Retransform-Classes:true

在startJavaAgent的方法中調(diào)用了啟動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;  
}  

在函數(shù)里药薯,實際上是調(diào)用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);
    }

繼續(xù)查看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 啟動并創(chuàng)建JVM,注冊vmInit回調(diào)函數(shù)

啟動并創(chuàng)建JVM

6.2 執(zhí)行vmInit回調(diào)函數(shù)童本,注冊jvmtiEventClassFileLoadHook回調(diào)函數(shù)真屯,加載并初始化 Instrument Agent

執(zhí)行vmInit回調(diào)函數(shù),注冊jvmtiEventClassFileLoadHook回調(diào)函數(shù)穷娱,加載并初始化 Instrument Agent

6.3 加載解析Class文件绑蔫,執(zhí)行jvmtiEventClassFileLoadHook回調(diào)函數(shù)

加載解析Class文件,執(zhí)行jvmtiEventClassFileLoadHook回調(diào)函數(shù)

6.4 以-javaagent為例泵额,工作原理

  1. 在JVM啟動時配深,通過JVM參數(shù)-javaagent,傳入agent jar嫁盲,Instrument Agent被加載篓叶;
  2. 在Instrument Agent 初始化時,注冊了JVMTI初始化函數(shù)eventHandlerVMinit亡资;
  3. 在JVM啟動時澜共,會調(diào)用初始化函數(shù)eventHandlerVMinit,啟動了Instrument Agent锥腻,用sun.instrument.instrumentationImpl類里的方法loadClassAndCallPremain方法去初始化Premain-Class指定類的premain方法嗦董;
  4. 初始化函數(shù)eventHandlerVMinit,注冊了class解析的ClassFileLoadHook函數(shù)瘦黑;
  5. 在解析Class之前京革,JVM調(diào)用JVMTI的ClassFileLoadHook函數(shù),鉤子函數(shù)調(diào)用sun.instrument.instrumentationImpl類里的transform方法幸斥,通過TransformerManager的transformer方法最終調(diào)用我們自定義的Transformer類的transform方法匹摇;
  6. 因為字節(jié)碼在解析Class之前改的,直接使用修改后的字節(jié)碼的數(shù)據(jù)流替代甲葬,最后進入Class解析廊勃,對整個Class解析無影響;
  7. 重新加載Class依然重新走5-6步驟经窖;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坡垫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子画侣,更是在濱河造成了極大的恐慌冰悠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件配乱,死亡現(xiàn)場離奇詭異溉卓,居然都是意外死亡皮迟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門桑寨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伏尼,“玉大人,你說我怎么就攤上這事尉尾》沉#” “怎么了甩鳄?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵峦树,是天一觀的道長搓茬。 經(jīng)常有香客問我,道長芭碍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任孽尽,我火速辦了婚禮窖壕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杉女。我一直安慰自己瞻讽,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布熏挎。 她就那樣靜靜地躺著速勇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坎拐。 梳的紋絲不亂的頭發(fā)上烦磁,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音哼勇,去河邊找鬼都伪。 笑死,一個胖子當著我的面吹牛积担,可吹牛的內(nèi)容都是我干的陨晶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼帝璧,長吁一口氣:“原來是場噩夢啊……” “哼先誉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起聋溜,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤谆膳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撮躁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漱病,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡买雾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杨帽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漓穿。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖注盈,靈堂內(nèi)的尸體忽然破棺而出晃危,到底是詐尸還是另有隱情,我是刑警寧澤老客,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布僚饭,位于F島的核電站,受9級特大地震影響胧砰,放射性物質(zhì)發(fā)生泄漏鳍鸵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一尉间、第九天 我趴在偏房一處隱蔽的房頂上張望偿乖。 院中可真熱鬧,春花似錦哲嘲、人聲如沸贪薪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽画切。三九已至,卻和暖如春囱怕,著一層夾襖步出監(jiān)牢的瞬間槽唾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工光涂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庞萍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓忘闻,卻偏偏與公主長得像钝计,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子齐佳,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容