本文主要是介绍Java Agent(五)OpenJdk/Instrument包源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
简介
在介绍instrument是什么之前,先来看几个定义:
JVMTI(JVM tool interface):它是JVM提供的一系列native编程接口。
Agent:与JVM进行通信的外部进程,它们通过调用JVMTI进行交互,可进行的操作包括设置JVM回调函数、获取当前虚拟机状态信息等
Instrument:Jdk提供的"java.lang.instrument"包,与JVM进行交互,可以看作为一个Instrument Agent。
https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/index.html?ca=drs-
整体流程
接上文,当JVM接收到execute(“load”,“instrument”,“false”,options)命令之后,开始进行动态链接库的加载。
- "load"命令——对应load_agent方法:
- 接收参数:op->arg(0)、 op->arg(1)、op->arg(2);(“instrument”、是否绝对路径、agentpath=args)
- 判断 if(arg(0)==“instrument”)先加载"java.instrument"模块,再load_agent_library
- load_agent_library:
- 首先加载动态库:“libinstrument.so”,加载instrument Agent(JVM定义Linux系统中,加载的动态链接库名字是"lib"+args(0)+".so")
- 调用库中的Agent_OnAttach方法。
从Agent_OnAttach开始,是libinstrument.so中的内容:
关于libInstrument.so库,我认为可以当做是一个底层Agent。
它直接调用了JVMTI提供的回调方法,然后加载我们自定义的agent,避免了我们自己的agent与JVMTI直接通信。
源码分析
// Implementation of "load" command.
static jint load_agent(AttachOperation* op, outputStream* out) {// get agent name and optionsconst char* agent = op->arg(0);const char* absParam = op->arg(1);const char* options = op->arg(2);// If loading a java agent then need to ensure that the java.instrument module is loadedif (strcmp(agent, "instrument") == 0) {Thread* THREAD = Thread::current();ResourceMark rm(THREAD);HandleMark hm(THREAD);JavaValue result(T_OBJECT);Handle h_module_name = java_lang_String::create_from_str("java.instrument", THREAD);JavaCalls::call_static(&result,SystemDictionary::module_Modules_klass(),vmSymbols::loadModule_name(),vmSymbols::loadModule_signature(),h_module_name,THREAD);if (HAS_PENDING_EXCEPTION) {java_lang_Throwable::print(PENDING_EXCEPTION, out);CLEAR_PENDING_EXCEPTION;return JNI_ERR;}}return JvmtiExport::load_agent_library(agent, absParam, options, out);
}
接收到的三个参数对应我们发送命令时的三个:“instrument”、是否绝对路径、agentpath=args;
先加载java.instrument模块,再加载Agent,调用JvmtiExport::load_agent_library(agent, absParam, options, out),当成功时返回码为0.
jint JvmtiExport::load_agent_library(const char *agent, const char *absParam,const char *options, outputStream* st) {char ebuf[1024];char buffer[JVM_MAXPATHLEN];void* library = NULL;jint result = JNI_ERR;const char *on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS;size_t num_symbol_entries = ARRAY_SIZE(on_attach_symbols);// The abs paramter should be "true" or "false"bool is_absolute_path = (absParam != NULL) && (strcmp(absParam,"true")==0);// Initially marked as invalid. It will be set to valid if we can find the agentAgentLibrary *agent_lib = new AgentLibrary(agent, options, is_absolute_path, NULL);// Check for statically linked in agent. If not found then if the path is// absolute we attempt to load the library. Otherwise we try to load it// from the standard dll directory.if (!os::find_builtin_agent(agent_lib, on_attach_symbols, num_symbol_entries)) {if (is_absolute_path) {library = os::dll_load(agent, ebuf, sizeof ebuf);} else {// Try to load the agent from the standard dll directoryif (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(),agent)) {library = os::dll_load(buffer, ebuf, sizeof ebuf);}if (library == NULL) {// not found - try OS default library pathif (os::dll_build_name(buffer, sizeof(buffer), agent)) {library = os::dll_load(buffer, ebuf, sizeof ebuf);}}}if (library != NULL) {agent_lib->set_os_lib(library);agent_lib->set_valid();}}// If the library was loaded then we attempt to invoke the Agent_OnAttach// functionif (agent_lib->valid()) {// Lookup the Agent_OnAttach functionOnAttachEntry_t on_attach_entry = NULL;on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t,os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries));if (on_attach_entry == NULL) {// Agent_OnAttach missing - unload libraryif (!agent_lib->is_static_lib()) {os::dll_unload(library);}delete agent_lib;} else {// Invoke the Agent_OnAttach functionJavaThread* THREAD = JavaThread::current();{extern struct JavaVM_ main_vm;JvmtiThreadEventMark jem(THREAD);JvmtiJavaThreadEventTransition jet(THREAD);result = (*on_attach_entry)(&main_vm, (char*)options, NULL);}// Agent_OnAttach may have used JNIif (HAS_PENDING_EXCEPTION) {CLEAR_PENDING_EXCEPTION;}// If OnAttach returns JNI_OK then we add it to the list of// agent libraries so that we can call Agent_OnUnload later.if (result == JNI_OK) {Arguments::add_loaded_agent(agent_lib);} else {delete agent_lib;}// Agent_OnAttach executed so completion status is JNI_OKst->print_cr("%d", result);result = JNI_OK;}}return result;
}
在linux中,首先要加载动态库,库的名字由dll_build_name(buffer, sizeof(buffer), agent)获得:“lib”+传入的agentlib名字+".so",即"libinstrument.so"。
on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t, os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries));这行代码负责在库中找Agent_OnAttach方法。
(find_agent_function支持找的方法:Agent_On(Un)Load/Attach)
libinstrument.so中的instrument agent,实现了Agent_OnLoad和Agent_OnAttach两方法,javaAgent功能就是由此实现的:
Agent_OnLoad对应虚拟机启动时加载,Agent_OnAttach对应启动后加载。
加载类库成功后,调用Agent_OnAttach方法。传参:vm、options(agentpath=args)、null
Agent_OnAttach:
/** This will be called once each time a tool attaches to the VM and loads* the JPLIS library.*/
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;jint result = JNI_OK;JPLISAgent * agent = NULL;JNIEnv * jni_env = NULL;/** Need JNIEnv - guaranteed to be called from thread that is already* attached to VM*/result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);jplis_assert(result==JNI_OK);initerror = createNewJPLISAgent(vm, &agent);if ( initerror == JPLIS_INIT_ERROR_NONE ) {int oldLen, newLen;char * jarfile;char * options;jarAttribute* attributes;char * agentClass;char * bootClassPath;jboolean success;/** Parse <jarfile>[=options] into jarfile and options*/if (parseArgumentTail(args, &jarfile, &options) != 0) {return JNI_ENOMEM;}/** Open the JAR file and parse the manifest*/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 AGENT_ERROR_BADJAR;}agentClass = getAttribute(attributes, "Agent-Class");if (agentClass == NULL) {fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",jarfile);free(jarfile);if (options != NULL) free(options);freeAttributes(attributes);return AGENT_ERROR_BADJAR;}/** Add the jarfile to the system class path*/if (appendClassPath(agent, jarfile)) {fprintf(stderr, "Unable to add %s to system class path ""- not supported by system class loader or configuration error!\n",jarfile);free(jarfile);if (options != NULL) free(options);freeAttributes(attributes);return AGENT_ERROR_NOTONCP;}/** The value of the Agent-Class attribute becomes the agent* class name. The manifest is in UTF8 so need to convert to* modified UTF8 (see JNI spec).*/oldLen = strlen(agentClass);newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);if (newLen == oldLen) {agentClass = strdup(agentClass);} else {char* str = (char*)malloc( newLen+1 );if (str != NULL) {convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);}agentClass = str;}if (agentClass == NULL) {free(jarfile);if (options != NULL) free(options);freeAttributes(attributes);return JNI_ENOMEM;}/** If the Boot-Class-Path attribute is specified then we process* each URL - in the live phase only JAR files will be added.*/bootClassPath = getAttribute(attributes, "Boot-Class-Path");if (bootClassPath != NULL) {appendBootClassPath(agent, jarfile, bootClassPath);}/** Convert JAR attributes into agent capabilities*/convertCapabilityAtrributes(attributes, agent);/** Create the java.lang.instrument.Instrumentation instance*/success = createInstrumentationImpl(jni_env, agent);jplis_assert(success);/** Turn on the ClassFileLoadHook.*/if (success) {success = setLivePhaseEventHandlers(agent);jplis_assert(success);}/** Start the agent*/if (success) {success = startJavaAgent(agent,jni_env,agentClass,options,agent->mAgentmainCaller);}if (!success) {fprintf(stderr, "Agent failed to start!\n");result = AGENT_ERROR_STARTFAIL;}/** Clean-up*/free(jarfile);if (options != NULL) free(options);free(agentClass);freeAttributes(attributes);}return result;
}
首先(parseArgumentTail(args, &jarfile, &options)解析传来的参数:将agentPath 与 args通过"="分隔;
- 打开Jar文件并解析manifest——attributes = readAttributes( jarfile );,
- 得到入口类——agentClass = getAttribute(attributes, “Agent-Class”)
- 将agentPath加入到System class path
- 创建Instrumention实例——success = createInstrumentationImpl(jni_env, agent);
- 打开ClassFileLoadHook——success = setLivePhaseEventHandlers(agent)
- 开始agent——startJavaAgent(agent,jni_env, agentClass,options, agent->mAgentmainCaller)
第五步:setLivePhaseEventHandlers
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);
}
JVM中定义的关于类加载的回调:钩子函数eventHandlerClassFileLoadHook
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);
第六步:
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;
}
其中的invokeJavaAgentMainMethod,通过调用mInstrumentationImpl.loadClassAndStartAgent实现初始化调用我们自定义的agentMain方法。
loadClassAndCallAgentmain( String classname,String optionsString)throws Throwable {loadClassAndStartAgent( classname, "agentmain", optionsString );
}// Attempt to load and start an agent
private void
loadClassAndStartAgent( String classname,String methodname,String optionsString)throws Throwable {ClassLoader mainAppLoader = ClassLoader.getSystemClassLoader();Class<?> javaAgentClass = mainAppLoader.loadClass(classname);Method m = null;NoSuchMethodException firstExc = null;boolean twoArgAgent = false;// The agent class must have a premain or agentmain method that// has 1 or 2 arguments. We check in the following order://// 1) declared with a signature of (String, Instrumentation)// 2) declared with a signature of (String)// 3) inherited with a signature of (String, Instrumentation)// 4) inherited with a signature of (String)//// So the declared version of either 1-arg or 2-arg always takes// primary precedence over an inherited version. After that, the// 2-arg version takes precedence over the 1-arg version.//// If no method is found then we throw the NoSuchMethodException// from the first attempt so that the exception text indicates// the lookup failed for the 2-arg method (same as JDK5.0).try {m = javaAgentClass.getDeclaredMethod( methodname,new Class<?>[] {String.class,java.lang.instrument.Instrumentation.class});twoArgAgent = true;} catch (NoSuchMethodException x) {// remember the NoSuchMethodExceptionfirstExc = x;}if (m == null) {// now try the declared 1-arg methodtry {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 methodtry {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 methodtry {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.0throw 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 methodif (twoArgAgent) {m.invoke(null, new Object[] { optionsString, this });} else {m.invoke(null, new Object[] { optionsString });}// don't let others access a non-public premain methodsetAccessible(m, false);
}
这篇关于Java Agent(五)OpenJdk/Instrument包源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!