Java Agent(五)OpenJdk/Instrument包源码分析

2024-02-23 11:18

本文主要是介绍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方法:
  1. 接收参数:op->arg(0)、 op->arg(1)、op->arg(2);(“instrument”、是否绝对路径、agentpath=args)
  2. 判断 if(arg(0)==“instrument”)先加载"java.instrument"模块,再load_agent_library
  • load_agent_library:
  1. 首先加载动态库:“libinstrument.so”,加载instrument Agent(JVM定义Linux系统中,加载的动态链接库名字是"lib"+args(0)+".so")
  2. 调用库中的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通过"="分隔;

  1. 打开Jar文件并解析manifest——attributes = readAttributes( jarfile );,
  2. 得到入口类——agentClass = getAttribute(attributes, “Agent-Class”)
  3. 将agentPath加入到System class path
  4. 创建Instrumention实例——success = createInstrumentationImpl(jni_env, agent);
  5. 打开ClassFileLoadHook——success = setLivePhaseEventHandlers(agent)
  6. 开始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包源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/738447

相关文章

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.