前一节讲述了基于JVMTI如何实现Agent,还有一种是基于Java Instrument API实现Agent,可以在Java代码层面编写Agent代码,而非基于C++/C的代码,具体使用可参考 《Java Instrument 功能使用及原理》 :
以 -javaagent :为开头的默认为instrument的agent;
那么以上这两种Agent实现方式,又是在JVMTI源码中如何运行工作呢?
在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 }
在启动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(); }
在方法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中 。
以下是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;
虚拟机在创建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()); } } } }
钩子方法是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。
如上,那么钩子函数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); } }
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; }
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
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 */ };
sun.instrument.InstrumentationImpl.loadClassAndCallPremain
方法,agent启动时加载会被调用该方法; sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain
方法,agent attach动态加载agent的时会被调用该方法; sun.instrument.InstrumentationImpl.transform
方法; Agent-Class
; Can-Redefine-Classes:true
; Can-Set-Native-Method-Prefix:true
; 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; } }
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); }